@strkfarm/sdk 1.0.56 → 1.0.57
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/index.browser.global.js +260 -83
- package/dist/index.browser.mjs +255 -66
- package/dist/index.d.ts +55 -9
- package/dist/index.js +255 -69
- package/dist/index.mjs +255 -69
- package/package.json +1 -1
- package/src/notifs/telegram.ts +0 -2
- package/src/strategies/base-strategy.ts +3 -23
- package/src/strategies/sensei.ts +13 -6
- package/src/strategies/universal-adapters/baseAdapter.ts +2 -1
- package/src/strategies/universal-adapters/vesu-adapter.ts +80 -10
- package/src/strategies/universal-strategy.ts +183 -31
- package/src/utils/cacheClass.ts +29 -0
- package/src/utils/oz-merkle.ts +0 -9
|
@@ -55,7 +55,6 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
55
55
|
config: VesuAdapterConfig;
|
|
56
56
|
networkConfig: IConfig | undefined;
|
|
57
57
|
pricer: PricerBase | undefined;
|
|
58
|
-
cache: Record<string, any> = {};
|
|
59
58
|
|
|
60
59
|
constructor(config: VesuAdapterConfig) {
|
|
61
60
|
super();
|
|
@@ -121,17 +120,19 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
121
120
|
denomination: this.formatAmountDenominationEnum(params.collateralAmount.denomination),
|
|
122
121
|
value: {
|
|
123
122
|
abs: uint256.bnToUint256(params.collateralAmount.value.abs.toWei()),
|
|
124
|
-
is_negative: params.collateralAmount.value.is_negative
|
|
123
|
+
is_negative: params.collateralAmount.value.abs.isZero() ? false: params.collateralAmount.value.is_negative
|
|
125
124
|
}
|
|
126
125
|
};
|
|
126
|
+
logger.verbose(`VesuAdapter::ConstructingModify::Collateral::${JSON.stringify(_collateral)}`)
|
|
127
127
|
const _debt = {
|
|
128
128
|
amount_type: this.formatAmountTypeEnum(params.debtAmount.amount_type),
|
|
129
129
|
denomination: this.formatAmountDenominationEnum(params.debtAmount.denomination),
|
|
130
130
|
value: {
|
|
131
131
|
abs: uint256.bnToUint256(params.debtAmount.value.abs.toWei()),
|
|
132
|
-
is_negative: params.debtAmount.value.is_negative
|
|
132
|
+
is_negative: params.debtAmount.value.abs.isZero() ? false: params.debtAmount.value.is_negative
|
|
133
133
|
}
|
|
134
134
|
};
|
|
135
|
+
logger.verbose(`VesuAdapter::ConstructingModify::Debt::${JSON.stringify(_debt)}`)
|
|
135
136
|
const singletonContract = new Contract(VesuSingletonAbi, this.VESU_SINGLETON.toString(), new RpcProvider({nodeUrl: ''}));
|
|
136
137
|
const call = singletonContract.populate('modify_position', {
|
|
137
138
|
params: {
|
|
@@ -182,13 +183,14 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
182
183
|
|
|
183
184
|
async getLTVConfig(config: IConfig) {
|
|
184
185
|
const CACHE_KEY = 'ltv_config';
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
const cacheData = this.getCache<number>(CACHE_KEY);
|
|
187
|
+
if (cacheData) {
|
|
188
|
+
return cacheData as number;
|
|
187
189
|
}
|
|
188
190
|
const output: any = await this.getVesuSingletonContract(config)
|
|
189
191
|
.call('ltv_config', [this.config.poolId.address, this.config.collateral.address.address, this.config.debt.address.address])
|
|
190
|
-
this.
|
|
191
|
-
return this.
|
|
192
|
+
this.setCache(CACHE_KEY, Number(output.max_ltv) / 1e18, 300000); // ttl: 5min
|
|
193
|
+
return this.getCache<number>(CACHE_KEY) as number;
|
|
192
194
|
}
|
|
193
195
|
|
|
194
196
|
async getPositions(config: IConfig): Promise<VaultPosition[]> {
|
|
@@ -197,8 +199,9 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
197
199
|
}
|
|
198
200
|
// { '0': { collateral_shares: 0n, nominal_debt: 0n }, '1': 0n, '2': 0n }
|
|
199
201
|
const CACHE_KEY = 'positions';
|
|
200
|
-
|
|
201
|
-
|
|
202
|
+
const cacheData = this.getCache<VaultPosition[]>(CACHE_KEY);
|
|
203
|
+
if (cacheData) {
|
|
204
|
+
return cacheData;
|
|
202
205
|
}
|
|
203
206
|
const output: any = await this.getVesuSingletonContract(config)
|
|
204
207
|
.call('position_unsafe', [
|
|
@@ -224,10 +227,77 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
224
227
|
usdValue: debtAmount.multipliedBy(token2Price.price).toNumber(),
|
|
225
228
|
remarks: "Debt"
|
|
226
229
|
}];
|
|
227
|
-
this.
|
|
230
|
+
this.setCache(CACHE_KEY, value, 60000); // ttl: 1min
|
|
228
231
|
return value;
|
|
229
232
|
}
|
|
230
233
|
|
|
234
|
+
async getCollateralization(config: IConfig): Promise<Omit<VaultPosition, 'amount'>[]> {
|
|
235
|
+
if (!this.pricer) {
|
|
236
|
+
throw new Error('Pricer is not initialized');
|
|
237
|
+
}
|
|
238
|
+
// { '0': bool, '1': 0n, '2': 0n }
|
|
239
|
+
const CACHE_KEY = 'collateralization';
|
|
240
|
+
const cacheData = this.getCache<Omit<VaultPosition, 'amount'>[]>(CACHE_KEY);
|
|
241
|
+
if (cacheData) {
|
|
242
|
+
return cacheData;
|
|
243
|
+
}
|
|
244
|
+
const output: any = await this.getVesuSingletonContract(config)
|
|
245
|
+
.call('check_collateralization_unsafe', [
|
|
246
|
+
this.config.poolId.address,
|
|
247
|
+
this.config.collateral.address.address,
|
|
248
|
+
this.config.debt.address.address,
|
|
249
|
+
this.config.vaultAllocator.address
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
// usd values
|
|
253
|
+
const collateralAmount = Web3Number.fromWei(output['1'].toString(), 18);
|
|
254
|
+
const debtAmount = Web3Number.fromWei(output['2'].toString(), 18);
|
|
255
|
+
const value = [{
|
|
256
|
+
token: this.config.collateral,
|
|
257
|
+
usdValue: collateralAmount.toNumber(),
|
|
258
|
+
remarks: "Collateral"
|
|
259
|
+
}, {
|
|
260
|
+
token: this.config.debt,
|
|
261
|
+
usdValue: debtAmount.toNumber(),
|
|
262
|
+
remarks: "Debt"
|
|
263
|
+
}];
|
|
264
|
+
this.setCache(CACHE_KEY, value, 60000); // ttl: 1min
|
|
265
|
+
return value;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async getAssetPrices() {
|
|
269
|
+
const collateralizationProm = this.getCollateralization(this.networkConfig!);
|
|
270
|
+
const positionsProm = this.getPositions(this.networkConfig!);
|
|
271
|
+
const ltvProm = this.getLTVConfig(this.networkConfig!);
|
|
272
|
+
|
|
273
|
+
const output = await Promise.all([collateralizationProm, positionsProm, ltvProm]);
|
|
274
|
+
const [collateralization, positions, ltv] = output;
|
|
275
|
+
|
|
276
|
+
const collateralTokenAmount = positions[0].amount;
|
|
277
|
+
const collateralUSDAmount = collateralization[0].usdValue;
|
|
278
|
+
const collateralPrice = collateralUSDAmount / collateralTokenAmount.toNumber();
|
|
279
|
+
|
|
280
|
+
const debtTokenAmount = positions[1].amount;
|
|
281
|
+
const debtUSDAmount = collateralization[1].usdValue;
|
|
282
|
+
const debtPrice = debtUSDAmount / debtTokenAmount.toNumber();
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
collateralTokenAmount,
|
|
286
|
+
collateralUSDAmount,
|
|
287
|
+
collateralPrice,
|
|
288
|
+
debtTokenAmount,
|
|
289
|
+
debtUSDAmount,
|
|
290
|
+
debtPrice,
|
|
291
|
+
ltv
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async getHealthFactor() {
|
|
296
|
+
const ltv = await this.getLTVConfig(this.networkConfig!);
|
|
297
|
+
const collateralisation = await this.getCollateralization(this.networkConfig!);
|
|
298
|
+
return collateralisation[0].usdValue * ltv / collateralisation[1].usdValue;
|
|
299
|
+
}
|
|
300
|
+
|
|
231
301
|
static async getVesuPools(
|
|
232
302
|
retry = 0
|
|
233
303
|
): Promise<VesuPoolsInfo> {
|
|
@@ -15,7 +15,9 @@ export interface UniversalStrategySettings {
|
|
|
15
15
|
vaultAllocator: ContractAddr,
|
|
16
16
|
redeemRequestNFT: ContractAddr,
|
|
17
17
|
leafAdapters: LeafAdapterFn<any>[],
|
|
18
|
-
adapters: {id: string, adapter: BaseAdapter}[]
|
|
18
|
+
adapters: {id: string, adapter: BaseAdapter}[],
|
|
19
|
+
targetHealthFactor: number,
|
|
20
|
+
minHealthFactor: number
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
|
|
@@ -166,7 +168,7 @@ export class UniversalStrategy<
|
|
|
166
168
|
* Calculates the weighted average APY across all pools based on USD value.
|
|
167
169
|
* @returns {Promise<number>} The weighted average APY across all pools
|
|
168
170
|
*/
|
|
169
|
-
async netAPY(): Promise<number> {
|
|
171
|
+
async netAPY(): Promise<{ net: number, splits: { apy: number, id: string }[] }> {
|
|
170
172
|
const [vesuAdapter1, vesuAdapter2] = this.getVesuAdapters();
|
|
171
173
|
const pools = await VesuAdapter.getVesuPools();
|
|
172
174
|
const pool1 = pools.pools.find(p => vesuAdapter1.config.poolId.eqString(num.getHexString(p.id)));
|
|
@@ -182,20 +184,32 @@ export class UniversalStrategy<
|
|
|
182
184
|
// supplyApy: { value: '8057256029163289', decimals: 18 },
|
|
183
185
|
// defiSpringSupplyApr: { value: '46856062629264560', decimals: 18 },
|
|
184
186
|
// borrowApr: { value: '12167825982336000', decimals: 18 },
|
|
185
|
-
const collateral1APY = Number(collateralAsset1.supplyApy.value) / 1e18
|
|
187
|
+
const collateral1APY = Number(collateralAsset1.supplyApy.value) / 1e18;
|
|
186
188
|
const debt1APY = Number(debtAsset1.borrowApr.value) / 1e18;
|
|
187
|
-
const collateral2APY = Number(collateralAsset2.supplyApy.value) / 1e18
|
|
189
|
+
const collateral2APY = Number(collateralAsset2.supplyApy.value) / 1e18;
|
|
188
190
|
const debt2APY = Number(debtAsset2.borrowApr.value) / 1e18;
|
|
189
191
|
|
|
190
|
-
const apys = [collateral1APY, debt1APY, collateral2APY, debt2APY];
|
|
191
192
|
const positions = await this.getVaultPositions();
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
const
|
|
198
|
-
|
|
193
|
+
const weights = positions.map((p, index) => p.usdValue * (index % 2 == 0 ? 1 : -1));
|
|
194
|
+
const baseAPYs = [collateral1APY, debt1APY, collateral2APY, debt2APY];
|
|
195
|
+
assert(positions.length == baseAPYs.length, "Positions and APYs length mismatch");
|
|
196
|
+
const rewardAPYs = [Number(collateralAsset1.defiSpringSupplyApr.value) / 1e18, 0, Number(collateralAsset2.defiSpringSupplyApr.value) / 1e18, 0];
|
|
197
|
+
const baseAPY = this.computeAPY(baseAPYs, weights);
|
|
198
|
+
const rewardAPY = this.computeAPY(rewardAPYs, weights);
|
|
199
|
+
const apys = [...baseAPYs, ...rewardAPYs];
|
|
200
|
+
const netAPY = baseAPY + rewardAPY;
|
|
201
|
+
return { net: netAPY, splits: [{
|
|
202
|
+
apy: baseAPY, id: 'base'
|
|
203
|
+
}, {
|
|
204
|
+
apy: rewardAPY, id: 'defispring'
|
|
205
|
+
}] };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private computeAPY(apys: number[], weights: number[]) {
|
|
209
|
+
assert(apys.length === weights.length, "APYs and weights length mismatch");
|
|
210
|
+
const weightedSum = apys.reduce((acc, apy, i) => acc + apy * weights[i], 0);
|
|
211
|
+
const totalWeight = weights.reduce((acc, weight) => acc + weight, 0);
|
|
212
|
+
return weightedSum / totalWeight;
|
|
199
213
|
}
|
|
200
214
|
|
|
201
215
|
/**
|
|
@@ -219,23 +233,53 @@ export class UniversalStrategy<
|
|
|
219
233
|
};
|
|
220
234
|
}
|
|
221
235
|
|
|
222
|
-
async getAUM(): Promise<SingleTokenInfo> {
|
|
236
|
+
async getAUM(): Promise<{net: SingleTokenInfo, prevAum: Web3Number, splits: {id: string, aum: Web3Number}[]}> {
|
|
237
|
+
const currentAUM: bigint = await this.contract.call('aum', []) as bigint;
|
|
238
|
+
const lastReportTime = await this.contract.call('last_report_timestamp', []);
|
|
239
|
+
|
|
240
|
+
const token1Price = await this.pricer.getPrice(this.metadata.depositTokens[0].symbol);
|
|
241
|
+
|
|
242
|
+
// calculate actual aum
|
|
223
243
|
const [vesuAdapter1, vesuAdapter2] = this.getVesuAdapters();
|
|
224
244
|
const leg1AUM = await vesuAdapter1.getPositions(this.config);
|
|
225
245
|
const leg2AUM = await vesuAdapter2.getPositions(this.config);
|
|
226
246
|
|
|
227
|
-
const token1Price = await this.pricer.getPrice(vesuAdapter1.config.collateral.symbol);
|
|
228
|
-
|
|
229
247
|
const aumToken = leg1AUM[0].amount
|
|
230
248
|
.plus(leg2AUM[0].usdValue / token1Price.price)
|
|
231
249
|
.minus(leg1AUM[1].usdValue / token1Price.price)
|
|
232
250
|
.minus(leg2AUM[1].amount);
|
|
233
|
-
|
|
234
|
-
|
|
251
|
+
logger.verbose(`${this.getTag()} Actual AUM: ${aumToken}`);
|
|
252
|
+
|
|
253
|
+
// calculate estimated growth from strk rewards
|
|
254
|
+
const netAPY = await this.netAPY();
|
|
255
|
+
const defispringAPY = netAPY.splits.find(s => s.id === 'defispring')?.apy || 0;
|
|
256
|
+
if (!defispringAPY) throw new Error('DefiSpring APY not found');
|
|
257
|
+
|
|
258
|
+
const timeDiff = (Math.round(Date.now() / 1000) - Number(lastReportTime));
|
|
259
|
+
const growthRate = timeDiff * defispringAPY / (365 * 24 * 60 * 60);
|
|
260
|
+
const prevAum = Web3Number.fromWei(currentAUM.toString(), this.asset().decimals);
|
|
261
|
+
const rewardAssets = prevAum.multipliedBy(growthRate);
|
|
262
|
+
logger.verbose(`${this.getTag()} DefiSpring AUM time difference: ${timeDiff}`);
|
|
263
|
+
logger.verbose(`${this.getTag()} Current AUM: ${currentAUM}`);
|
|
264
|
+
logger.verbose(`${this.getTag()} Net APY: ${JSON.stringify(netAPY)}`);
|
|
265
|
+
logger.verbose(`${this.getTag()} rewards AUM: ${rewardAssets}`);
|
|
266
|
+
|
|
267
|
+
const newAUM = aumToken.plus(rewardAssets);
|
|
268
|
+
logger.verbose(`${this.getTag()} New AUM: ${newAUM}`);
|
|
269
|
+
|
|
270
|
+
const net = {
|
|
235
271
|
tokenInfo: this.asset(),
|
|
236
|
-
amount:
|
|
237
|
-
usdValue:
|
|
238
|
-
}
|
|
272
|
+
amount: newAUM,
|
|
273
|
+
usdValue: newAUM.multipliedBy(token1Price.price).toNumber()
|
|
274
|
+
};
|
|
275
|
+
const splits = [{
|
|
276
|
+
id: 'finalised',
|
|
277
|
+
aum: aumToken
|
|
278
|
+
}, {
|
|
279
|
+
id: 'defispring',
|
|
280
|
+
aum: rewardAssets
|
|
281
|
+
}];
|
|
282
|
+
return { net, splits, prevAum };
|
|
239
283
|
}
|
|
240
284
|
|
|
241
285
|
getVesuAdapters() {
|
|
@@ -243,6 +287,8 @@ export class UniversalStrategy<
|
|
|
243
287
|
const vesuAdapter2 = this.getAdapter(UNIVERSAL_ADAPTERS.VESU_LEG2) as VesuAdapter;
|
|
244
288
|
vesuAdapter1.pricer = this.pricer;
|
|
245
289
|
vesuAdapter2.pricer = this.pricer;
|
|
290
|
+
vesuAdapter1.networkConfig = this.config;
|
|
291
|
+
vesuAdapter2.networkConfig = this.config;
|
|
246
292
|
|
|
247
293
|
return [vesuAdapter1, vesuAdapter2];
|
|
248
294
|
}
|
|
@@ -297,12 +343,14 @@ export class UniversalStrategy<
|
|
|
297
343
|
|
|
298
344
|
const output = [{
|
|
299
345
|
proofs: manage5Info.proofs,
|
|
300
|
-
manageCall: manageCall5
|
|
346
|
+
manageCall: manageCall5,
|
|
347
|
+
step: STEP2_ID
|
|
301
348
|
}];
|
|
302
349
|
if (approveAmount.gt(0)) {
|
|
303
350
|
output.unshift({
|
|
304
351
|
proofs: manage4Info.proofs,
|
|
305
|
-
manageCall: manageCall4
|
|
352
|
+
manageCall: manageCall4,
|
|
353
|
+
step: STEP1_ID
|
|
306
354
|
})
|
|
307
355
|
}
|
|
308
356
|
return output;
|
|
@@ -312,12 +360,114 @@ export class UniversalStrategy<
|
|
|
312
360
|
return `${UniversalStrategy.name}:${this.metadata.name}`;
|
|
313
361
|
}
|
|
314
362
|
|
|
363
|
+
async getVesuHealthFactors() {
|
|
364
|
+
return await Promise.all(this.getVesuAdapters().map(v => v.getHealthFactor()));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async computeRebalanceConditionAndReturnCalls(): Promise<Call[]> {
|
|
368
|
+
const vesuAdapters = this.getVesuAdapters();
|
|
369
|
+
const healthFactors = await this.getVesuHealthFactors();
|
|
370
|
+
const leg1HealthFactor = healthFactors[0];
|
|
371
|
+
const leg2HealthFactor = healthFactors[1];
|
|
372
|
+
logger.verbose(`${this.getTag()}: HealthFactorLeg1: ${leg1HealthFactor}`);
|
|
373
|
+
logger.verbose(`${this.getTag()}: HealthFactorLeg2: ${leg2HealthFactor}`);
|
|
374
|
+
|
|
375
|
+
const minHf = this.metadata.additionalInfo.minHealthFactor;
|
|
376
|
+
const isRebalanceNeeded1 = leg1HealthFactor < minHf;
|
|
377
|
+
const isRebalanceNeeded2 = leg2HealthFactor < minHf;
|
|
378
|
+
if (!isRebalanceNeeded1 && !isRebalanceNeeded2) {
|
|
379
|
+
return [];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (isRebalanceNeeded1) {
|
|
383
|
+
const amount = await this.getLegRebalanceAmount(vesuAdapters[0], leg1HealthFactor, false);
|
|
384
|
+
const leg2HF = await this.getNewHealthFactor(vesuAdapters[1], amount, true);
|
|
385
|
+
assert(leg2HF > minHf, `Rebalance Leg1 failed: Leg2 HF after rebalance would be too low: ${leg2HF}`);
|
|
386
|
+
return [await this.getRebalanceCall({
|
|
387
|
+
isLeg1toLeg2: false,
|
|
388
|
+
amount: amount
|
|
389
|
+
})];
|
|
390
|
+
} else {
|
|
391
|
+
const amount = await this.getLegRebalanceAmount(vesuAdapters[1], leg2HealthFactor, true);
|
|
392
|
+
const leg1HF = await this.getNewHealthFactor(vesuAdapters[0], amount, false);
|
|
393
|
+
assert(leg1HF > minHf, `Rebalance Leg2 failed: Leg1 HF after rebalance would be too low: ${leg1HF}`);
|
|
394
|
+
return [await this.getRebalanceCall({
|
|
395
|
+
isLeg1toLeg2: true,
|
|
396
|
+
amount: amount
|
|
397
|
+
})];
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
private async getNewHealthFactor(vesuAdapter: VesuAdapter, newAmount: Web3Number, isWithdraw: boolean) {
|
|
402
|
+
const {
|
|
403
|
+
collateralTokenAmount,
|
|
404
|
+
collateralUSDAmount,
|
|
405
|
+
collateralPrice,
|
|
406
|
+
debtTokenAmount,
|
|
407
|
+
debtUSDAmount,
|
|
408
|
+
debtPrice,
|
|
409
|
+
ltv
|
|
410
|
+
} = await vesuAdapter.getAssetPrices();
|
|
411
|
+
|
|
412
|
+
if (isWithdraw) {
|
|
413
|
+
const newHF = ((collateralTokenAmount.toNumber() - newAmount.toNumber()) * collateralPrice * ltv) / debtUSDAmount;
|
|
414
|
+
logger.verbose(`getNewHealthFactor:: HF: ${newHF}, amoutn: ${newAmount.toNumber()}, isDeposit`);
|
|
415
|
+
return newHF;
|
|
416
|
+
} else { // is borrow
|
|
417
|
+
const newHF = (collateralUSDAmount * ltv) / ((debtTokenAmount.toNumber() + newAmount.toNumber()) * debtPrice);
|
|
418
|
+
logger.verbose(`getNewHealthFactor:: HF: ${newHF}, amoutn: ${newAmount.toNumber()}, isRepay`);
|
|
419
|
+
return newHF;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
*
|
|
425
|
+
* @param vesuAdapter
|
|
426
|
+
* @param currentHf
|
|
427
|
+
* @param isDeposit if true, attempt by adding collateral, else by repaying
|
|
428
|
+
* @returns
|
|
429
|
+
*/
|
|
430
|
+
private async getLegRebalanceAmount(vesuAdapter: VesuAdapter, currentHf: number, isDeposit: boolean) {
|
|
431
|
+
const {
|
|
432
|
+
collateralTokenAmount,
|
|
433
|
+
collateralUSDAmount,
|
|
434
|
+
collateralPrice,
|
|
435
|
+
debtTokenAmount,
|
|
436
|
+
debtUSDAmount,
|
|
437
|
+
debtPrice,
|
|
438
|
+
ltv
|
|
439
|
+
} = await vesuAdapter.getAssetPrices();
|
|
440
|
+
|
|
441
|
+
// debt is zero, nothing to rebalance
|
|
442
|
+
if(debtTokenAmount.isZero()) {
|
|
443
|
+
return Web3Number.fromWei(0, 0);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
assert(collateralPrice > 0 && debtPrice > 0, "getRebalanceAmount: Invalid price");
|
|
447
|
+
|
|
448
|
+
// avoid calculating for too close
|
|
449
|
+
const targetHF = this.metadata.additionalInfo.targetHealthFactor;
|
|
450
|
+
if (currentHf > targetHF - 0.01)
|
|
451
|
+
throw new Error("getLegRebalanceAmount: Current health factor is healthy");
|
|
452
|
+
|
|
453
|
+
if (isDeposit) {
|
|
454
|
+
// TargetHF = (collAmount + newAmount) * price * ltv / debtUSD
|
|
455
|
+
const newAmount = targetHF * debtUSDAmount / (collateralPrice * ltv) - collateralTokenAmount.toNumber();
|
|
456
|
+
logger.verbose(`${this.getTag()}:: getLegRebalanceAmount: addCollateral, currentHf: ${currentHf}, targetHF: ${targetHF}, collAmount: ${collateralTokenAmount.toString()}, collUSD: ${collateralUSDAmount}, collPrice: ${collateralPrice}, debtAmount: ${debtTokenAmount.toString()}, debtUSD: ${debtUSDAmount}, debtPrice: ${debtPrice}, ltv: ${ltv}, newAmount: ${newAmount}`);
|
|
457
|
+
return new Web3Number(newAmount.toFixed(8), collateralTokenAmount.decimals);
|
|
458
|
+
} else {
|
|
459
|
+
// TargetHF = collUSD * ltv / (debtAmount - newAmount) * debtPrice
|
|
460
|
+
const newAmount = debtTokenAmount.toNumber() - collateralUSDAmount * ltv / (targetHF * debtPrice);
|
|
461
|
+
logger.verbose(`${this.getTag()}:: getLegRebalanceAmount: repayDebt, currentHf: ${currentHf}, targetHF: ${targetHF}, collAmount: ${collateralTokenAmount.toString()}, collUSD: ${collateralUSDAmount}, collPrice: ${collateralPrice}, debtAmount: ${debtTokenAmount.toString()}, debtUSD: ${debtUSDAmount}, debtPrice: ${debtPrice}, ltv: ${ltv}, newAmount: ${newAmount}`);
|
|
462
|
+
return new Web3Number(newAmount.toFixed(8), debtTokenAmount.decimals);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
315
466
|
async getVesuMultiplyCall(params: {
|
|
316
467
|
isDeposit: boolean,
|
|
317
468
|
leg1DepositAmount: Web3Number
|
|
318
469
|
}) {
|
|
319
|
-
const vesuAdapter1 = this.
|
|
320
|
-
const vesuAdapter2 = this.getAdapter(UNIVERSAL_ADAPTERS.VESU_LEG2) as VesuAdapter;
|
|
470
|
+
const [vesuAdapter1, vesuAdapter2] = this.getVesuAdapters();
|
|
321
471
|
const leg1LTV = await vesuAdapter1.getLTVConfig(this.config);
|
|
322
472
|
const leg2LTV = await vesuAdapter2.getLTVConfig(this.config);
|
|
323
473
|
logger.verbose(`${this.getTag()}: LTVLeg1: ${leg1LTV}`);
|
|
@@ -328,7 +478,7 @@ export class UniversalStrategy<
|
|
|
328
478
|
logger.verbose(`${this.getTag()}: Price${vesuAdapter1.config.collateral.symbol}: ${token1Price.price}`);
|
|
329
479
|
logger.verbose(`${this.getTag()}: Price${vesuAdapter2.config.collateral.symbol}: ${token2Price.price}`);
|
|
330
480
|
|
|
331
|
-
const TARGET_HF =
|
|
481
|
+
const TARGET_HF = this.metadata.additionalInfo.targetHealthFactor;
|
|
332
482
|
|
|
333
483
|
const k1 = token1Price.price * leg1LTV / token2Price.price / TARGET_HF;
|
|
334
484
|
const k2 = token1Price.price * TARGET_HF / token2Price.price / leg2LTV;
|
|
@@ -399,14 +549,12 @@ export class UniversalStrategy<
|
|
|
399
549
|
|
|
400
550
|
if (params.isLeg1toLeg2) {
|
|
401
551
|
const manageCall = this.getManageCall([
|
|
402
|
-
|
|
403
|
-
UNIVERSAL_MANAGE_IDS.VESU_LEG2
|
|
552
|
+
...callSet1.map(i => i.step), ...callSet2.map(i => i.step)
|
|
404
553
|
], [...callSet1.map(i => i.manageCall), ...callSet2.map(i => i.manageCall)]);
|
|
405
554
|
return manageCall;
|
|
406
555
|
} else {
|
|
407
556
|
const manageCall = this.getManageCall([
|
|
408
|
-
|
|
409
|
-
UNIVERSAL_MANAGE_IDS.VESU_LEG1
|
|
557
|
+
...callSet2.map(i => i.step), ...callSet1.map(i => i.step)
|
|
410
558
|
], [...callSet2.map(i => i.manageCall), ...callSet1.map(i => i.manageCall)]);
|
|
411
559
|
return manageCall;
|
|
412
560
|
}
|
|
@@ -485,7 +633,9 @@ const usdcVaultSettings: UniversalStrategySettings = {
|
|
|
485
633
|
vaultAllocator: ContractAddr.from('0x228cca1005d3f2b55cbaba27cb291dacf1b9a92d1d6b1638195fbd3d0c1e3ba'),
|
|
486
634
|
redeemRequestNFT: ContractAddr.from('0x906d03590010868cbf7590ad47043959d7af8e782089a605d9b22567b64fda'),
|
|
487
635
|
leafAdapters: [],
|
|
488
|
-
adapters: []
|
|
636
|
+
adapters: [],
|
|
637
|
+
targetHealthFactor: 1.3,
|
|
638
|
+
minHealthFactor: 1.25
|
|
489
639
|
}
|
|
490
640
|
|
|
491
641
|
const wbtcVaultSettings: UniversalStrategySettings = {
|
|
@@ -493,7 +643,9 @@ const wbtcVaultSettings: UniversalStrategySettings = {
|
|
|
493
643
|
vaultAllocator: ContractAddr.from('0x1e01c25f0d9494570226ad28a7fa856c0640505e809c366a9fab4903320e735'),
|
|
494
644
|
redeemRequestNFT: ContractAddr.from('0x4fec59a12f8424281c1e65a80b5de51b4e754625c60cddfcd00d46941ec37b2'),
|
|
495
645
|
leafAdapters: [],
|
|
496
|
-
adapters: []
|
|
646
|
+
adapters: [],
|
|
647
|
+
targetHealthFactor: 1.3,
|
|
648
|
+
minHealthFactor: 1.25
|
|
497
649
|
}
|
|
498
650
|
|
|
499
651
|
export const UniversalStrategies: IStrategyMetadata<UniversalStrategySettings>[] =
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
interface CacheData {
|
|
2
|
+
timestamp: number;
|
|
3
|
+
ttl: number;
|
|
4
|
+
data: any;
|
|
5
|
+
}
|
|
6
|
+
export class CacheClass {
|
|
7
|
+
readonly cache: Map<string, CacheData> = new Map();
|
|
8
|
+
|
|
9
|
+
setCache(key: string, data: any, ttl: number = 60000): void {
|
|
10
|
+
const timestamp = Date.now();
|
|
11
|
+
this.cache.set(key, { timestamp, ttl, data });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getCache<T>(key: string): T | null {
|
|
15
|
+
const cachedData = this.cache.get(key);
|
|
16
|
+
if (!cachedData || !this.isCacheValid(key)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return cachedData.data;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
isCacheValid(key: string): boolean {
|
|
23
|
+
const cachedData = this.cache.get(key);
|
|
24
|
+
if (!cachedData) return false;
|
|
25
|
+
|
|
26
|
+
const { timestamp, ttl } = cachedData;
|
|
27
|
+
return Date.now() - timestamp <= ttl;
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/utils/oz-merkle.ts
CHANGED
|
@@ -59,15 +59,6 @@ export class StandardMerkleTree extends MerkleTreeImpl<LeafData> {
|
|
|
59
59
|
return new StandardMerkleTree(tree, indexedValues, leafEncoding);
|
|
60
60
|
}
|
|
61
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
62
|
static verify<T extends any[]>(root: BytesLike, leafEncoding: ValueType[], leaf: T, proof: BytesLike[]): boolean {
|
|
72
63
|
// use default nodeHash (standardNodeHash) for processProof
|
|
73
64
|
return toHex(root) === processProof(standardLeafHash(leafEncoding, leaf), proof);
|