@strkfarm/sdk 1.2.0 → 2.0.0-dca.1
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 +9 -5
- package/dist/cli.mjs +9 -5
- package/dist/index.browser.global.js +65510 -38933
- package/dist/index.browser.mjs +5218 -1908
- package/dist/index.d.ts +478 -33
- package/dist/index.js +5499 -2156
- package/dist/index.mjs +5441 -2129
- package/package.json +4 -1
- package/src/data/ekubo-price-fethcer.abi.json +265 -0
- package/src/data/yoloVault.abi.json +777 -0
- package/src/dataTypes/_bignumber.ts +5 -0
- package/src/dataTypes/bignumber.browser.ts +5 -0
- package/src/dataTypes/bignumber.node.ts +5 -0
- package/src/dataTypes/index.ts +3 -2
- package/src/dataTypes/mynumber.ts +141 -0
- package/src/global.ts +42 -0
- package/src/index.browser.ts +2 -1
- package/src/interfaces/common.tsx +168 -2
- package/src/modules/apollo-client-config.ts +28 -0
- package/src/modules/avnu.ts +1 -1
- package/src/modules/ekubo-pricer.ts +79 -0
- package/src/modules/erc20.ts +18 -2
- package/src/modules/pragma.ts +23 -8
- package/src/modules/pricer-from-api.ts +150 -14
- package/src/modules/pricer.ts +2 -1
- package/src/modules/pricerBase.ts +2 -1
- package/src/node/pricer-redis.ts +2 -1
- package/src/strategies/base-strategy.ts +81 -2
- package/src/strategies/ekubo-cl-vault.tsx +686 -316
- package/src/strategies/factory.ts +159 -0
- package/src/strategies/index.ts +5 -1
- package/src/strategies/registry.ts +239 -0
- package/src/strategies/sensei.ts +361 -13
- package/src/strategies/types.ts +4 -0
- package/src/strategies/universal-adapters/vesu-adapter.ts +48 -27
- package/src/strategies/universal-lst-muliplier-strategy.tsx +1396 -462
- package/src/strategies/universal-strategy.tsx +287 -129
- package/src/strategies/vesu-rebalance.tsx +242 -146
- package/src/strategies/yoloVault.ts +463 -0
- package/src/utils/index.ts +1 -1
- package/src/utils/logger.node.ts +11 -4
- package/src/utils/strategy-utils.ts +61 -0
package/src/strategies/sensei.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { getNoRiskTags, highlightTextWithLinks, IConfig, IProtocol, IStrategyMetadata, RiskFactor, RiskType, TokenInfo } from "@/interfaces";
|
|
1
|
+
import { getNoRiskTags, highlightTextWithLinks, IConfig, IProtocol, IStrategyMetadata, RiskFactor, RiskType, StrategyTag, TokenInfo, AuditStatus, SourceCodeType, AccessControlType, InstantWithdrawalVault, StrategyLiveStatus, VaultType } from "@/interfaces";
|
|
2
2
|
import { BaseStrategy, SingleActionAmount, SingleTokenInfo } from "./base-strategy";
|
|
3
3
|
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
4
|
-
import { Call, Contract, uint256 } from "starknet";
|
|
4
|
+
import { Call, Contract, num, uint256, BlockIdentifier } from "starknet";
|
|
5
5
|
import SenseiABI from "@/data/sensei.abi.json";
|
|
6
6
|
import { getTrovesEndpoint, logger } from "@/utils";
|
|
7
7
|
import { Global } from "@/global";
|
|
@@ -9,6 +9,10 @@ import { QuoteRequest } from "@avnu/avnu-sdk";
|
|
|
9
9
|
import { PricerBase } from "@/modules/pricerBase";
|
|
10
10
|
import ERC20ABI from "@/data/erc20.abi.json";
|
|
11
11
|
import { AvnuWrapper } from "@/modules";
|
|
12
|
+
import { gql } from "@apollo/client";
|
|
13
|
+
import apolloClient from "@/modules/apollo-client";
|
|
14
|
+
import { VesuAdapter, VesuPools } from "./universal-adapters/vesu-adapter";
|
|
15
|
+
import { LSTAPRService } from "@/modules/lst-apr";
|
|
12
16
|
|
|
13
17
|
export interface SenseiVaultSettings {
|
|
14
18
|
mainToken: TokenInfo;
|
|
@@ -37,17 +41,27 @@ export class SenseiVault extends BaseStrategy<
|
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
async getUserTVL(user: ContractAddr): Promise<SingleTokenInfo> {
|
|
44
|
+
async getUserTVL(user: ContractAddr, blockIdentifier: BlockIdentifier = "latest"): Promise<SingleTokenInfo> {
|
|
41
45
|
const result: any = await this.contract.call(
|
|
42
46
|
"describe_position",
|
|
43
47
|
[user.address],
|
|
48
|
+
{
|
|
49
|
+
blockIdentifier,
|
|
50
|
+
}
|
|
44
51
|
);
|
|
45
52
|
const amount = Web3Number.fromWei(
|
|
46
53
|
uint256.uint256ToBN(result[1].estimated_size).toString(),
|
|
47
54
|
this.metadata.depositTokens[0].decimals,
|
|
48
55
|
)
|
|
56
|
+
|
|
57
|
+
// Convert blockIdentifier to block number for pricer if it's a number
|
|
58
|
+
const blockNumber = typeof blockIdentifier === 'number' || typeof blockIdentifier === 'bigint'
|
|
59
|
+
? Number(blockIdentifier)
|
|
60
|
+
: undefined;
|
|
61
|
+
|
|
49
62
|
const price = await this.pricer.getPrice(
|
|
50
63
|
this.metadata.depositTokens[0].symbol,
|
|
64
|
+
blockNumber
|
|
51
65
|
);
|
|
52
66
|
return {
|
|
53
67
|
usdValue: Number(amount.toFixed(6)) * price.price,
|
|
@@ -144,23 +158,42 @@ export class SenseiVault extends BaseStrategy<
|
|
|
144
158
|
throw new Error('No positions found');
|
|
145
159
|
}
|
|
146
160
|
|
|
147
|
-
|
|
161
|
+
let collateralXSTRK = Web3Number.fromWei(
|
|
148
162
|
data.data[0].collateral.value,
|
|
149
163
|
data.data[0].collateral.decimals,
|
|
150
164
|
);
|
|
151
|
-
|
|
165
|
+
let collateralUSDValue = Web3Number.fromWei(
|
|
152
166
|
data.data[0].collateral.usdPrice.value,
|
|
153
167
|
data.data[0].collateral.usdPrice.decimals,
|
|
154
168
|
);
|
|
155
|
-
|
|
169
|
+
let debtSTRK = Web3Number.fromWei(
|
|
156
170
|
data.data[0].debt.value,
|
|
157
171
|
data.data[0].debt.decimals,
|
|
158
172
|
);
|
|
159
|
-
|
|
173
|
+
let debtUSDValue = Web3Number.fromWei(
|
|
160
174
|
data.data[0].debt.usdPrice.value,
|
|
161
175
|
data.data[0].debt.usdPrice.decimals,
|
|
162
176
|
);
|
|
163
177
|
|
|
178
|
+
if (data.data[1]) {
|
|
179
|
+
collateralXSTRK = collateralXSTRK.plus(Web3Number.fromWei(
|
|
180
|
+
data.data[1].collateral.value,
|
|
181
|
+
data.data[1].collateral.decimals,
|
|
182
|
+
));
|
|
183
|
+
collateralUSDValue = collateralUSDValue.plus(Web3Number.fromWei(
|
|
184
|
+
data.data[1].collateral.usdPrice.value,
|
|
185
|
+
data.data[1].collateral.usdPrice.decimals,
|
|
186
|
+
));
|
|
187
|
+
debtSTRK = debtSTRK.plus(Web3Number.fromWei(
|
|
188
|
+
data.data[1].debt.value,
|
|
189
|
+
data.data[1].debt.decimals,
|
|
190
|
+
));
|
|
191
|
+
debtUSDValue = debtUSDValue.plus(Web3Number.fromWei(
|
|
192
|
+
data.data[1].debt.usdPrice.value,
|
|
193
|
+
data.data[1].debt.usdPrice.decimals,
|
|
194
|
+
));
|
|
195
|
+
}
|
|
196
|
+
|
|
164
197
|
const xSTRKPrice = await this.getSecondaryTokenPriceRelativeToMain();
|
|
165
198
|
const collateralInSTRK =
|
|
166
199
|
Number(collateralXSTRK.toFixed(6)) * xSTRKPrice;
|
|
@@ -189,10 +222,11 @@ export class SenseiVault extends BaseStrategy<
|
|
|
189
222
|
const existingCacheData = this.getCache<number>(CACHE_KEY);
|
|
190
223
|
if (existingCacheData) return existingCacheData;
|
|
191
224
|
|
|
225
|
+
let AMOUNT = 100;
|
|
192
226
|
const params: QuoteRequest = {
|
|
193
227
|
sellTokenAddress: this.metadata.additionalInfo.secondaryToken.address.address,
|
|
194
228
|
buyTokenAddress: this.metadata.additionalInfo.mainToken.address.address,
|
|
195
|
-
sellAmount: BigInt(new Web3Number(
|
|
229
|
+
sellAmount: BigInt(new Web3Number(AMOUNT.toString(), 18).toWei()),
|
|
196
230
|
takerAddress: this.address.address,
|
|
197
231
|
};
|
|
198
232
|
logger.verbose('getSecondaryTokenPriceRelativeToMain [1]', params);
|
|
@@ -212,7 +246,7 @@ export class SenseiVault extends BaseStrategy<
|
|
|
212
246
|
Web3Number.fromWei(firstQuote.buyAmount.toString(), 18).toFixed(
|
|
213
247
|
6,
|
|
214
248
|
),
|
|
215
|
-
);
|
|
249
|
+
) / AMOUNT;
|
|
216
250
|
logger.verbose('getSecondaryTokenPriceRelativeToMain [2]', price);
|
|
217
251
|
this.setCache(CACHE_KEY, price); // cache for 1 min
|
|
218
252
|
return price;
|
|
@@ -223,6 +257,268 @@ export class SenseiVault extends BaseStrategy<
|
|
|
223
257
|
logger.verbose('getSettings', settings);
|
|
224
258
|
return settings;
|
|
225
259
|
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Calculate lifetime earnings for a user
|
|
263
|
+
* Not yet implemented for Sensei Vault strategy
|
|
264
|
+
*/
|
|
265
|
+
getLifetimeEarnings(
|
|
266
|
+
userTVL: SingleTokenInfo,
|
|
267
|
+
investmentFlows: Array<{ amount: string; type: string; timestamp: number; tx_hash: string }>
|
|
268
|
+
): any {
|
|
269
|
+
throw new Error("getLifetimeEarnings is not implemented yet for this strategy");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async netAPY(): Promise<number> {
|
|
273
|
+
try {
|
|
274
|
+
// Fetch Vesu pools and select the Re7 xSTRK pool
|
|
275
|
+
const { pools } = await VesuAdapter.getVesuPools();
|
|
276
|
+
const re7PoolId = VesuPools.Re7xSTRK;
|
|
277
|
+
const pool = pools.find((p: any) =>
|
|
278
|
+
ContractAddr.from(num.getHexString(p.id)).eq(re7PoolId),
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
if (!pool) {
|
|
282
|
+
logger.warn(`${SenseiVault.name}::netAPY - Re7 xSTRK pool not found`);
|
|
283
|
+
return 0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const mainSymbol = this.metadata.additionalInfo.mainToken.symbol.toLowerCase(); // STRK
|
|
287
|
+
const secondarySymbol =
|
|
288
|
+
this.metadata.additionalInfo.secondaryToken.symbol.toLowerCase(); // xSTRK
|
|
289
|
+
|
|
290
|
+
const collateralAssetStats = pool.assets.find(
|
|
291
|
+
(a: any) => String(a.symbol).toLowerCase() === secondarySymbol,
|
|
292
|
+
)?.stats;
|
|
293
|
+
const debtAssetStats = pool.assets.find(
|
|
294
|
+
(a: any) => String(a.symbol).toLowerCase() === mainSymbol,
|
|
295
|
+
)?.stats;
|
|
296
|
+
|
|
297
|
+
if (!collateralAssetStats || !debtAssetStats) {
|
|
298
|
+
logger.warn(
|
|
299
|
+
`${SenseiVault.name}::netAPY - Missing collateral/debt stats on Vesu pool`,
|
|
300
|
+
);
|
|
301
|
+
return 0;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Base xSTRK lending APY from Vesu
|
|
305
|
+
const xstrkSupplyAPY =
|
|
306
|
+
Number(collateralAssetStats.supplyApy?.value || 0) / 1e18;
|
|
307
|
+
|
|
308
|
+
// STRK rewards APR on xSTRK collateral from Vesu
|
|
309
|
+
const strkRewardsAPR = collateralAssetStats.defiSpringSupplyApr
|
|
310
|
+
? Number(collateralAssetStats.defiSpringSupplyApr.value || 0) / 1e18
|
|
311
|
+
: 0;
|
|
312
|
+
|
|
313
|
+
// STRK borrow APY from Vesu
|
|
314
|
+
const borrowAPY =
|
|
315
|
+
Number(debtAssetStats.borrowApr?.value || 0) / 1e18;
|
|
316
|
+
|
|
317
|
+
// LST APR for xSTRK from Endur (based on underlying STRK asset)
|
|
318
|
+
const lstAPY = await LSTAPRService.getLSTAPR(
|
|
319
|
+
this.metadata.additionalInfo.mainToken.address,
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
// Collateral APY = Vesu xSTRK supply + Endur xSTRK APR + STRK rewards
|
|
323
|
+
const collateralAPY = xstrkSupplyAPY + lstAPY + strkRewardsAPR;
|
|
324
|
+
|
|
325
|
+
const feeFactor = this.metadata.additionalInfo.feeBps / 10000; // convert bps to decimal
|
|
326
|
+
const feeAdjustedColAPY =
|
|
327
|
+
collateralAPY - strkRewardsAPR * feeFactor;
|
|
328
|
+
|
|
329
|
+
// Position info (collateral & debt in USD terms)
|
|
330
|
+
const { collateralUSDValue, debtUSDValue } =
|
|
331
|
+
await this.getPositionInfo();
|
|
332
|
+
|
|
333
|
+
const collateralUSD = Number(collateralUSDValue.toFixed(6));
|
|
334
|
+
const debtUSD = Number(debtUSDValue.toFixed(6));
|
|
335
|
+
|
|
336
|
+
// Compute expected leverage using the same math as app-side strategy
|
|
337
|
+
const targetHf = this.metadata.additionalInfo.targetHfBps / 10000;
|
|
338
|
+
const xSTRKPrice = await this.getSecondaryTokenPriceRelativeToMain();
|
|
339
|
+
const denominator = targetHf * xSTRKPrice - 0.87;
|
|
340
|
+
if (denominator <= 0) {
|
|
341
|
+
logger.warn(
|
|
342
|
+
`${SenseiVault.name}::netAPY - Invalid denominator in leverage calc`,
|
|
343
|
+
);
|
|
344
|
+
return 0;
|
|
345
|
+
}
|
|
346
|
+
const borrowedSTRK = (0.87 * xSTRKPrice) / denominator;
|
|
347
|
+
const expectedLeverage = 1 + borrowedSTRK;
|
|
348
|
+
if (!Number.isFinite(expectedLeverage) || expectedLeverage <= 0) {
|
|
349
|
+
logger.warn(
|
|
350
|
+
`${SenseiVault.name}::netAPY - Non-positive or invalid expectedLeverage`,
|
|
351
|
+
);
|
|
352
|
+
return 0;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const payoff =
|
|
356
|
+
collateralUSD * feeAdjustedColAPY - debtUSD * borrowAPY;
|
|
357
|
+
const investment = collateralUSD - debtUSD;
|
|
358
|
+
|
|
359
|
+
if (investment === 0) {
|
|
360
|
+
return 0;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const netAPY = payoff / investment;
|
|
364
|
+
return Number.isFinite(netAPY) ? netAPY : 0;
|
|
365
|
+
} catch (error) {
|
|
366
|
+
logger.error(`${SenseiVault.name}::netAPY - Error`, error);
|
|
367
|
+
return 0;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Calculates user realized APY based on position growth accounting for deposits and withdrawals.
|
|
373
|
+
* Returns the APY as a number.
|
|
374
|
+
* Not implemented for Sensei Strategy yet.
|
|
375
|
+
*/
|
|
376
|
+
async getUserRealizedAPY(
|
|
377
|
+
blockIdentifier: BlockIdentifier = "latest",
|
|
378
|
+
sinceBlocks = 600000
|
|
379
|
+
): Promise<number> {
|
|
380
|
+
throw new Error("getUserRealizedAPY not implemented yet for Sensei strategy");
|
|
381
|
+
|
|
382
|
+
/*
|
|
383
|
+
logger.verbose(
|
|
384
|
+
`${SenseiVault.name}: getUserRealizedAPY => starting with userAddress=${userAddress.address}, blockIdentifier=${blockIdentifier}, sinceBlocks=${sinceBlocks}`
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
// Determine current block number
|
|
388
|
+
let blockNow =
|
|
389
|
+
typeof blockIdentifier === "number" || typeof blockIdentifier === "bigint"
|
|
390
|
+
? Number(blockIdentifier)
|
|
391
|
+
: (await this.config.provider.getBlockLatestAccepted()).block_number;
|
|
392
|
+
|
|
393
|
+
// Look back window, but never before launch block
|
|
394
|
+
const blockBefore = Math.max(
|
|
395
|
+
blockNow - sinceBlocks,
|
|
396
|
+
this.metadata.launchBlock
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
logger.verbose(
|
|
400
|
+
`${SenseiVault.name}: getUserRealizedAPY => blockNow=${blockNow}, blockBefore=${blockBefore}`
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
// Get current estimated size
|
|
404
|
+
const currentResult: any = await this.contract.call(
|
|
405
|
+
"describe_position",
|
|
406
|
+
[userAddress.address],
|
|
407
|
+
{
|
|
408
|
+
blockIdentifier,
|
|
409
|
+
}
|
|
410
|
+
);
|
|
411
|
+
const currentEstimatedSize = Web3Number.fromWei(
|
|
412
|
+
uint256.uint256ToBN(currentResult[1].estimated_size).toString(),
|
|
413
|
+
this.metadata.depositTokens[0].decimals,
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
// Get previous estimated size
|
|
417
|
+
const previousResult: any = await this.contract.call(
|
|
418
|
+
"describe_position",
|
|
419
|
+
[userAddress.address],
|
|
420
|
+
{
|
|
421
|
+
blockIdentifier: blockBefore,
|
|
422
|
+
}
|
|
423
|
+
);
|
|
424
|
+
const previousEstimatedSize = Web3Number.fromWei(
|
|
425
|
+
uint256.uint256ToBN(previousResult[1].estimated_size).toString(),
|
|
426
|
+
this.metadata.depositTokens[0].decimals,
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
logger.verbose(
|
|
430
|
+
`${SenseiVault.name}: getUserRealizedAPY => currentEstimatedSize=${currentEstimatedSize.toString()}, previousEstimatedSize=${previousEstimatedSize.toString()}`
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
// Query GraphQL for deposits and withdrawals between blockBefore and blockNow
|
|
434
|
+
let newDeposits = Web3Number.fromWei("0", this.metadata.depositTokens[0].decimals);
|
|
435
|
+
let newWithdrawals = Web3Number.fromWei("0", this.metadata.depositTokens[0].decimals);
|
|
436
|
+
|
|
437
|
+
try {
|
|
438
|
+
const { data } = await apolloClient.query({
|
|
439
|
+
query: gql`
|
|
440
|
+
query Query($where: Investment_flowsWhereInput) {
|
|
441
|
+
findManyInvestment_flows(where: $where) {
|
|
442
|
+
block_number
|
|
443
|
+
amount
|
|
444
|
+
type
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
`,
|
|
448
|
+
variables: {
|
|
449
|
+
where: {
|
|
450
|
+
contract: {
|
|
451
|
+
equals: this.address.address.toLowerCase(),
|
|
452
|
+
},
|
|
453
|
+
owner: {
|
|
454
|
+
equals: userAddress.address.toLowerCase(),
|
|
455
|
+
},
|
|
456
|
+
block_number: {
|
|
457
|
+
gte: blockBefore,
|
|
458
|
+
lte: blockNow,
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
fetchPolicy: 'no-cache',
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// Sum deposits and withdrawals
|
|
466
|
+
for (const flow of data.findManyInvestment_flows) {
|
|
467
|
+
const amount = Web3Number.fromWei(
|
|
468
|
+
flow.amount,
|
|
469
|
+
this.metadata.depositTokens[0].decimals
|
|
470
|
+
);
|
|
471
|
+
if (flow.type === 'deposit') {
|
|
472
|
+
newDeposits = newDeposits.plus(amount);
|
|
473
|
+
} else if (flow.type === 'withdraw') {
|
|
474
|
+
newWithdrawals = newWithdrawals.plus(amount);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
logger.verbose(
|
|
479
|
+
`${SenseiVault.name}: getUserRealizedAPY => newDeposits=${newDeposits.toString()}, newWithdrawals=${newWithdrawals.toString()}`
|
|
480
|
+
);
|
|
481
|
+
} catch (error) {
|
|
482
|
+
logger.verbose(
|
|
483
|
+
`${SenseiVault.name}: getUserRealizedAPY => Error querying GraphQL, continuing with zero deposits/withdrawals:`,
|
|
484
|
+
error
|
|
485
|
+
);
|
|
486
|
+
// Continue with zero deposits/withdrawals if GraphQL query fails
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Calculate growth: Current estimated size - new deposits + new withdrawals - previous estimated size
|
|
490
|
+
const growth = currentEstimatedSize
|
|
491
|
+
.minus(newDeposits)
|
|
492
|
+
.plus(newWithdrawals)
|
|
493
|
+
.minus(previousEstimatedSize);
|
|
494
|
+
|
|
495
|
+
logger.verbose(
|
|
496
|
+
`${SenseiVault.name}: getUserRealizedAPY => growth=${growth.toString()}`
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
// Handle edge case where previous position is zero
|
|
500
|
+
if (previousEstimatedSize.isZero() || previousEstimatedSize.lte(0)) {
|
|
501
|
+
logger.verbose(
|
|
502
|
+
`${SenseiVault.name}: getUserRealizedAPY => Previous position is zero, returning 0`
|
|
503
|
+
);
|
|
504
|
+
return 0;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Calculate actual block difference (in case limited by launch block)
|
|
508
|
+
const actualBlockDiff = blockNow - blockBefore;
|
|
509
|
+
|
|
510
|
+
// Calculate APY: 100 * 365/n * growth / previousEstimatedSize
|
|
511
|
+
// where n is the number of blocks (sinceBlocks or actual block difference)
|
|
512
|
+
const growthRatio = growth.dividedBy(previousEstimatedSize);
|
|
513
|
+
const apy = Number(growthRatio) * 100 * 365 / actualBlockDiff;
|
|
514
|
+
|
|
515
|
+
logger.verbose(
|
|
516
|
+
`${SenseiVault.name}: getUserRealizedAPY => actualBlockDiff=${actualBlockDiff}, growthRatio=${Number(growthRatio)}, apy=${apy}`
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
return apy;
|
|
520
|
+
*/
|
|
521
|
+
}
|
|
226
522
|
|
|
227
523
|
}
|
|
228
524
|
|
|
@@ -289,9 +585,10 @@ const FAQS = [
|
|
|
289
585
|
export const SenseiStrategies: IStrategyMetadata<SenseiVaultSettings>[] =
|
|
290
586
|
[
|
|
291
587
|
{
|
|
588
|
+
id: "xstrk_sensei",
|
|
292
589
|
name: "xSTRK Sensei",
|
|
293
590
|
description: highlightTextWithLinks(
|
|
294
|
-
senseiDescription.
|
|
591
|
+
senseiDescription.replace('{{token1}}', 'STRK').replace('{{token2}}', 'xSTRK'),
|
|
295
592
|
[{
|
|
296
593
|
highlight: "Endur",
|
|
297
594
|
link: "https://endur.fi"
|
|
@@ -308,11 +605,37 @@ export const SenseiStrategies: IStrategyMetadata<SenseiVaultSettings>[] =
|
|
|
308
605
|
),
|
|
309
606
|
launchBlock: 1053811,
|
|
310
607
|
type: "Other",
|
|
608
|
+
curator: {
|
|
609
|
+
name: "Unwrap Labs",
|
|
610
|
+
logo: "https://assets.troves.fi/integrations/unwraplabs/white.png"
|
|
611
|
+
},
|
|
612
|
+
vaultType: {
|
|
613
|
+
type: VaultType.LOOPING,
|
|
614
|
+
description: "Creates leveraged looping position on xSTRK by borrowing STRK to increase yield"
|
|
615
|
+
},
|
|
311
616
|
depositTokens: [
|
|
312
617
|
Global.getDefaultTokens().find((t) => t.symbol === "STRK")!
|
|
313
618
|
],
|
|
314
619
|
protocols: [endurProtocol, vesuProtocol],
|
|
315
|
-
|
|
620
|
+
settings: {
|
|
621
|
+
maxTVL: new Web3Number("1500000", 18),
|
|
622
|
+
alerts: [
|
|
623
|
+
{
|
|
624
|
+
type: "info",
|
|
625
|
+
text: "Depeg-risk: If xSTRK price on DEXes deviates from expected price, you may lose money or may have to wait for the price to recover.",
|
|
626
|
+
tab: "all"
|
|
627
|
+
}
|
|
628
|
+
],
|
|
629
|
+
liveStatus: StrategyLiveStatus.ACTIVE,
|
|
630
|
+
isPaused: false,
|
|
631
|
+
isInMaintenance: false,
|
|
632
|
+
isAudited: false,
|
|
633
|
+
isInstantWithdrawal: true,
|
|
634
|
+
isTransactionHistDisabled: true,
|
|
635
|
+
quoteToken: Global.getDefaultTokens().find(
|
|
636
|
+
(t) => t.symbol === "STRK"
|
|
637
|
+
)!
|
|
638
|
+
},
|
|
316
639
|
risk: {
|
|
317
640
|
riskFactor: _riskFactor,
|
|
318
641
|
netRisk:
|
|
@@ -335,6 +658,31 @@ export const SenseiStrategies: IStrategyMetadata<SenseiVaultSettings>[] =
|
|
|
335
658
|
"Buy more xSTRK with borrowed STRK",
|
|
336
659
|
"Repeat the process to loop your position",
|
|
337
660
|
"Claim DeFi spring (STRK) rewards weekly and reinvest",
|
|
338
|
-
]
|
|
661
|
+
],
|
|
662
|
+
tags: [StrategyTag.LEVERED],
|
|
663
|
+
security: {
|
|
664
|
+
auditStatus: AuditStatus.AUDITED,
|
|
665
|
+
sourceCode: {
|
|
666
|
+
type: SourceCodeType.CLOSED_SOURCE,
|
|
667
|
+
contractLink: "https://github.com/trovesfi/troves-contracts",
|
|
668
|
+
},
|
|
669
|
+
accessControl: {
|
|
670
|
+
type: AccessControlType.STANDARD_ACCOUNT,
|
|
671
|
+
addresses: [ContractAddr.from("0x0")],
|
|
672
|
+
timeLock: "2 Days",
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
redemptionInfo: {
|
|
676
|
+
instantWithdrawalVault: InstantWithdrawalVault.YES,
|
|
677
|
+
redemptionsInfo: [],
|
|
678
|
+
alerts: [],
|
|
679
|
+
},
|
|
680
|
+
usualTimeToEarnings: "2 weeks",
|
|
681
|
+
usualTimeToEarningsDescription: "Strategy returns depend on LST price on DEXes. Even though the true price of LST on Endur increases continuously, the DEX price may lag sometimes, and historically is seen to rebase at least once every 2 hours. This is when you realise your earnings.",
|
|
682
|
+
points: [{
|
|
683
|
+
multiplier: 4,
|
|
684
|
+
logo: 'https://endur.fi/favicon.ico',
|
|
685
|
+
toolTip: "This strategy holds xSTRK. Earn 3-4x Endur points on your xSTRK due to the leverage. Points can be found on endur.fi.",
|
|
686
|
+
}]
|
|
339
687
|
},
|
|
340
|
-
];
|
|
688
|
+
];
|
|
@@ -234,6 +234,25 @@ export const VesuPools = {
|
|
|
234
234
|
Re7xSTRK: ContractAddr.from('0x052fb52363939c3aa848f8f4ac28f0a51379f8d1b971d8444de25fbd77d8f161'),
|
|
235
235
|
Re7xBTC: ContractAddr.from('0x3a8416bf20d036df5b1cf3447630a2e1cb04685f6b0c3a70ed7fb1473548ecf'),
|
|
236
236
|
Prime: ContractAddr.from('0x451fe483d5921a2919ddd81d0de6696669bccdacd859f72a4fba7656b97c3b5'),
|
|
237
|
+
Re7STRK: ContractAddr.from('0x01fcdacc1d8184eca7b472b5acbaf1500cec9d5683ca95fede8128b46c8f9cc2'),
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export const VesuPoolMetadata = {
|
|
241
|
+
[VesuPools.Genesis.address]: {
|
|
242
|
+
name: 'Genesis',
|
|
243
|
+
},
|
|
244
|
+
[VesuPools.Re7xSTRK.address]: {
|
|
245
|
+
name: 'Re7 xSTRK',
|
|
246
|
+
},
|
|
247
|
+
[VesuPools.Re7xBTC.address]: {
|
|
248
|
+
name: 'Re7 xBTC',
|
|
249
|
+
},
|
|
250
|
+
[VesuPools.Prime.address]: {
|
|
251
|
+
name: 'Prime',
|
|
252
|
+
},
|
|
253
|
+
[VesuPools.Re7STRK.address]: {
|
|
254
|
+
name: 'Re7 STRK',
|
|
255
|
+
},
|
|
237
256
|
}
|
|
238
257
|
|
|
239
258
|
export const extensionMap: {[key: string]: ContractAddr} = {};
|
|
@@ -418,14 +437,14 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
418
437
|
}
|
|
419
438
|
}
|
|
420
439
|
|
|
421
|
-
getVesuModifyDelegationAdapter = (id: string): LeafAdapterFn<VesuModifyDelegationCallParams> => {
|
|
440
|
+
getVesuModifyDelegationAdapter = (id: string, delegatee: ContractAddr): LeafAdapterFn<VesuModifyDelegationCallParams> => {
|
|
422
441
|
return () => {
|
|
423
442
|
const { addr: VESU_SINGLETON, isV2 } = getVesuSingletonAddress(this.config.poolId);
|
|
424
443
|
const packedArguments: bigint[] = isV2 ? [
|
|
425
|
-
toBigInt(
|
|
444
|
+
toBigInt(delegatee.toString()), // v2
|
|
426
445
|
] : [
|
|
427
446
|
this.config.poolId.toBigInt(),
|
|
428
|
-
toBigInt(
|
|
447
|
+
toBigInt(delegatee.toString()), // v1
|
|
429
448
|
];
|
|
430
449
|
const output = this.constructSimpleLeafData({
|
|
431
450
|
id: id,
|
|
@@ -434,29 +453,31 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
434
453
|
packedArguments
|
|
435
454
|
}, isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER_VESU_V1_DELEGATIONS);
|
|
436
455
|
|
|
437
|
-
return { leaf: output, callConstructor: this.getVesuModifyDelegationCall.bind(this) };
|
|
456
|
+
return { leaf: output, callConstructor: this.getVesuModifyDelegationCall(delegatee).bind(this) };
|
|
438
457
|
}
|
|
439
458
|
}
|
|
440
459
|
|
|
441
|
-
getVesuModifyDelegationCall = (
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
+
getVesuModifyDelegationCall = (delegatee: ContractAddr) => {
|
|
461
|
+
return (params: VesuModifyDelegationCallParams) => {
|
|
462
|
+
const VESU_SINGLETON = getVesuSingletonAddress(this.config.poolId).addr;
|
|
463
|
+
const { contract, isV2 } = this.getVesuSingletonContract(getMainnetConfig(), this.config.poolId);
|
|
464
|
+
const call = contract.populate('modify_delegation', isV2 ? {
|
|
465
|
+
delegatee: delegatee.toBigInt(),
|
|
466
|
+
delegation: params.delegation,
|
|
467
|
+
} : {
|
|
468
|
+
pool_id: this.config.poolId.toBigInt(),
|
|
469
|
+
delegatee: delegatee.toBigInt(),
|
|
470
|
+
delegation: params.delegation,
|
|
471
|
+
});
|
|
472
|
+
return {
|
|
473
|
+
sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER_VESU_V1_DELEGATIONS,
|
|
474
|
+
call: {
|
|
475
|
+
contractAddress: VESU_SINGLETON,
|
|
476
|
+
selector: hash.getSelectorFromName('modify_delegation'),
|
|
477
|
+
calldata: [
|
|
478
|
+
...call.calldata as bigint[]
|
|
479
|
+
]
|
|
480
|
+
}
|
|
460
481
|
}
|
|
461
482
|
}
|
|
462
483
|
}
|
|
@@ -654,12 +675,12 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
654
675
|
amount: collateralAmount,
|
|
655
676
|
token: this.config.collateral,
|
|
656
677
|
usdValue: collateralAmount.multipliedBy(token1Price.price).toNumber(),
|
|
657
|
-
remarks:
|
|
678
|
+
remarks: `Collateral - ${VesuPoolMetadata[this.config.poolId.address].name} pool`
|
|
658
679
|
}, {
|
|
659
680
|
amount: debtAmount,
|
|
660
681
|
token: this.config.debt,
|
|
661
682
|
usdValue: debtAmount.multipliedBy(token2Price.price).toNumber(),
|
|
662
|
-
remarks:
|
|
683
|
+
remarks: `Debt - ${VesuPoolMetadata[this.config.poolId.address].name} pool`
|
|
663
684
|
}];
|
|
664
685
|
this.setCache(CACHE_KEY, value, 60000); // ttl: 1min
|
|
665
686
|
return value;
|
|
@@ -747,7 +768,7 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
747
768
|
let pools: any[] = [];
|
|
748
769
|
try {
|
|
749
770
|
const data = await getAPIUsingHeadlessBrowser(
|
|
750
|
-
`${ENDPOINTS.
|
|
771
|
+
`${ENDPOINTS.VESU_BASE}/pools`
|
|
751
772
|
);
|
|
752
773
|
pools = data.data;
|
|
753
774
|
|
|
@@ -893,4 +914,4 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
893
914
|
}
|
|
894
915
|
throw new Error('Max utilization not found');
|
|
895
916
|
}
|
|
896
|
-
}
|
|
917
|
+
}
|