@strkfarm/sdk 2.0.0-staging.1 → 2.0.0-staging.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 +10 -6
- package/dist/cli.mjs +10 -6
- package/dist/index.browser.global.js +53062 -28963
- package/dist/index.browser.mjs +2429 -1799
- package/dist/index.d.ts +188 -46
- package/dist/index.js +2122 -1479
- package/dist/index.mjs +2698 -2068
- package/package.json +79 -81
- package/src/dataTypes/index.ts +3 -2
- package/src/dataTypes/mynumber.ts +141 -0
- package/src/global.ts +27 -1
- package/src/index.browser.ts +2 -1
- package/src/interfaces/common.tsx +98 -13
- package/src/modules/ekubo-quoter.ts +1 -1
- package/src/modules/harvests.ts +17 -14
- package/src/modules/index.ts +0 -1
- package/src/modules/pricer-lst.ts +1 -1
- package/src/modules/pricer.ts +38 -3
- package/src/strategies/base-strategy.ts +25 -0
- package/src/strategies/ekubo-cl-vault.tsx +547 -269
- package/src/strategies/factory.ts +159 -0
- package/src/strategies/index.ts +1 -0
- package/src/strategies/registry.ts +50 -113
- package/src/strategies/sensei.ts +134 -7
- package/src/strategies/universal-adapters/vesu-adapter.ts +7 -5
- package/src/strategies/universal-lst-muliplier-strategy.tsx +279 -118
- package/src/strategies/universal-strategy.tsx +144 -144
- package/src/strategies/vesu-rebalance.tsx +95 -150
- package/src/utils/index.ts +1 -0
- package/src/utils/logger.node.ts +11 -4
- package/src/utils/strategy-utils.ts +57 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { IConfig, IStrategyMetadata } from "@/interfaces";
|
|
2
|
+
import { PricerBase } from "@/modules/pricerBase";
|
|
3
|
+
import {
|
|
4
|
+
UniversalStrategy,
|
|
5
|
+
UniversalStrategySettings
|
|
6
|
+
} from "./universal-strategy";
|
|
7
|
+
import { EkuboCLVault, CLVaultStrategySettings } from "./ekubo-cl-vault";
|
|
8
|
+
import {
|
|
9
|
+
UniversalLstMultiplierStrategy,
|
|
10
|
+
HyperLSTStrategySettings
|
|
11
|
+
} from "./universal-lst-muliplier-strategy";
|
|
12
|
+
import { VesuRebalance, VesuRebalanceSettings } from "./vesu-rebalance";
|
|
13
|
+
import { SenseiVault, SenseiVaultSettings } from "./sensei";
|
|
14
|
+
|
|
15
|
+
export enum FactoryStrategyType {
|
|
16
|
+
UNIVERSAL = "UNIVERSAL",
|
|
17
|
+
EKUBO_CL = "EKUBO_CL",
|
|
18
|
+
HYPER_LST = "HYPER_LST",
|
|
19
|
+
VESU_REBALANCE = "VESU_REBALANCE",
|
|
20
|
+
SENSEI = "SENSEI"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createUniversalStrategy(
|
|
24
|
+
config: IConfig,
|
|
25
|
+
pricer: PricerBase,
|
|
26
|
+
metadata: IStrategyMetadata<UniversalStrategySettings>
|
|
27
|
+
): UniversalStrategy<UniversalStrategySettings> {
|
|
28
|
+
return new UniversalStrategy(config, pricer, metadata);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createEkuboCLStrategy(
|
|
32
|
+
config: IConfig,
|
|
33
|
+
pricer: PricerBase,
|
|
34
|
+
metadata: IStrategyMetadata<CLVaultStrategySettings>
|
|
35
|
+
): EkuboCLVault {
|
|
36
|
+
return new EkuboCLVault(config, pricer, metadata);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createHyperLSTStrategy(
|
|
40
|
+
config: IConfig,
|
|
41
|
+
pricer: PricerBase,
|
|
42
|
+
metadata: IStrategyMetadata<HyperLSTStrategySettings>
|
|
43
|
+
): UniversalLstMultiplierStrategy {
|
|
44
|
+
return new UniversalLstMultiplierStrategy(config, pricer, metadata);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createVesuRebalanceStrategy(
|
|
48
|
+
config: IConfig,
|
|
49
|
+
pricer: PricerBase,
|
|
50
|
+
metadata: IStrategyMetadata<VesuRebalanceSettings>
|
|
51
|
+
): VesuRebalance {
|
|
52
|
+
return new VesuRebalance(config, pricer, metadata);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createSenseiStrategy(
|
|
56
|
+
config: IConfig,
|
|
57
|
+
pricer: PricerBase,
|
|
58
|
+
metadata: IStrategyMetadata<SenseiVaultSettings>
|
|
59
|
+
): SenseiVault {
|
|
60
|
+
return new SenseiVault(config, pricer, metadata);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Determines the strategy type from metadata by inspecting the additionalInfo structure
|
|
65
|
+
*/
|
|
66
|
+
export function getStrategyTypeFromMetadata(
|
|
67
|
+
metadata: IStrategyMetadata<any>
|
|
68
|
+
): FactoryStrategyType {
|
|
69
|
+
const info = metadata.additionalInfo;
|
|
70
|
+
|
|
71
|
+
// Check for HyperLST (extends UniversalStrategySettings with borrowable_assets and underlyingToken)
|
|
72
|
+
if (info && "borrowable_assets" in info && "underlyingToken" in info) {
|
|
73
|
+
return FactoryStrategyType.HYPER_LST;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check for EkuboCL (has newBounds, rebalanceConditions, quoteAsset)
|
|
77
|
+
if (
|
|
78
|
+
info &&
|
|
79
|
+
("newBounds" in info || typeof info.newBounds !== "undefined") &&
|
|
80
|
+
"rebalanceConditions" in info &&
|
|
81
|
+
"quoteAsset" in info
|
|
82
|
+
) {
|
|
83
|
+
return FactoryStrategyType.EKUBO_CL;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check for Sensei (has mainToken, secondaryToken, targetHfBps)
|
|
87
|
+
if (
|
|
88
|
+
info &&
|
|
89
|
+
"mainToken" in info &&
|
|
90
|
+
"secondaryToken" in info &&
|
|
91
|
+
"targetHfBps" in info
|
|
92
|
+
) {
|
|
93
|
+
return FactoryStrategyType.SENSEI;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check for VesuRebalance (has feeBps, but simpler structure)
|
|
97
|
+
if (info && "feeBps" in info && Object.keys(info).length <= 2) {
|
|
98
|
+
return FactoryStrategyType.VESU_REBALANCE;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check for Universal (has vaultAddress, manager, vaultAllocator, etc.)
|
|
102
|
+
if (
|
|
103
|
+
info &&
|
|
104
|
+
"vaultAddress" in info &&
|
|
105
|
+
"manager" in info &&
|
|
106
|
+
"vaultAllocator" in info
|
|
107
|
+
) {
|
|
108
|
+
return FactoryStrategyType.UNIVERSAL;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Unable to determine strategy type from metadata: ${metadata.id}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Generic factory function that creates SDK strategy instances based on type
|
|
118
|
+
*/
|
|
119
|
+
export function createStrategy(
|
|
120
|
+
type: FactoryStrategyType,
|
|
121
|
+
config: IConfig,
|
|
122
|
+
pricer: PricerBase,
|
|
123
|
+
metadata: IStrategyMetadata<any>
|
|
124
|
+
) {
|
|
125
|
+
switch (type) {
|
|
126
|
+
case FactoryStrategyType.UNIVERSAL:
|
|
127
|
+
return createUniversalStrategy(
|
|
128
|
+
config,
|
|
129
|
+
pricer,
|
|
130
|
+
metadata as IStrategyMetadata<UniversalStrategySettings>
|
|
131
|
+
);
|
|
132
|
+
case FactoryStrategyType.EKUBO_CL:
|
|
133
|
+
return createEkuboCLStrategy(
|
|
134
|
+
config,
|
|
135
|
+
pricer,
|
|
136
|
+
metadata as IStrategyMetadata<CLVaultStrategySettings>
|
|
137
|
+
);
|
|
138
|
+
case FactoryStrategyType.HYPER_LST:
|
|
139
|
+
return createHyperLSTStrategy(
|
|
140
|
+
config,
|
|
141
|
+
pricer,
|
|
142
|
+
metadata as IStrategyMetadata<HyperLSTStrategySettings>
|
|
143
|
+
);
|
|
144
|
+
case FactoryStrategyType.VESU_REBALANCE:
|
|
145
|
+
return createVesuRebalanceStrategy(
|
|
146
|
+
config,
|
|
147
|
+
pricer,
|
|
148
|
+
metadata as IStrategyMetadata<VesuRebalanceSettings>
|
|
149
|
+
);
|
|
150
|
+
case FactoryStrategyType.SENSEI:
|
|
151
|
+
return createSenseiStrategy(
|
|
152
|
+
config,
|
|
153
|
+
pricer,
|
|
154
|
+
metadata as IStrategyMetadata<SenseiVaultSettings>
|
|
155
|
+
);
|
|
156
|
+
default:
|
|
157
|
+
throw new Error(`Unknown strategy type: ${type}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
package/src/strategies/index.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Strategy Registry - Centralized strategy metadata and filtering
|
|
3
|
-
* Provides utilities for getting strategy lists, metadata, and filter options
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { IStrategyMetadata } from "@/interfaces";
|
|
1
|
+
import { getAllStrategyTags, IStrategyMetadata } from "@/interfaces";
|
|
7
2
|
import {
|
|
8
3
|
EkuboCLVaultStrategies,
|
|
9
4
|
CLVaultStrategySettings,
|
|
@@ -31,7 +26,6 @@ export interface FilterOption {
|
|
|
31
26
|
export interface StrategyFilterMetadata {
|
|
32
27
|
assets: FilterOption[];
|
|
33
28
|
protocols: FilterOption[];
|
|
34
|
-
categories: FilterOption[];
|
|
35
29
|
quickFilters: FilterOption[];
|
|
36
30
|
}
|
|
37
31
|
|
|
@@ -55,7 +49,6 @@ export interface StrategyMetadata {
|
|
|
55
49
|
type: StrategyType;
|
|
56
50
|
assets: string[]; // Token symbols (e.g., ["STRK", "USDC"])
|
|
57
51
|
protocols: string[]; // Protocol names (e.g., ["Ekubo"])
|
|
58
|
-
category: string; // Category (e.g., "BTC", "Meta Vaults")
|
|
59
52
|
tags: string[];
|
|
60
53
|
curator?: { name: string; logo: string };
|
|
61
54
|
isRetired: boolean;
|
|
@@ -166,7 +159,6 @@ export function getAllStrategyMetadata(): StrategyMetadata[] {
|
|
|
166
159
|
type: entry.type,
|
|
167
160
|
assets: extractAssets(entry.metadata),
|
|
168
161
|
protocols: entry.metadata.protocols.map(p => p.name.toLowerCase()),
|
|
169
|
-
category: entry.metadata.category,
|
|
170
162
|
tags: entry.metadata.tags || [],
|
|
171
163
|
curator: entry.metadata.curator,
|
|
172
164
|
isRetired: isRetired(entry.metadata),
|
|
@@ -178,62 +170,55 @@ export function getAllStrategyMetadata(): StrategyMetadata[] {
|
|
|
178
170
|
* Get filter metadata - defines available filters and their options
|
|
179
171
|
*/
|
|
180
172
|
export function getFilterMetadata(): StrategyFilterMetadata {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return {
|
|
231
|
-
assets,
|
|
232
|
-
protocols,
|
|
233
|
-
categories,
|
|
234
|
-
quickFilters,
|
|
235
|
-
};
|
|
236
|
-
}
|
|
173
|
+
const allMetadata = getAllStrategyMetadata();
|
|
174
|
+
|
|
175
|
+
// Extract unique assets
|
|
176
|
+
const assetSet = new Set<string>();
|
|
177
|
+
allMetadata.forEach((meta) => {
|
|
178
|
+
meta.assets.forEach((asset) => assetSet.add(asset));
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const allowedAssets = new Set(["eth", "btc", "strk", "usdt", "usdc"]);
|
|
182
|
+
const assets: FilterOption[] = Array.from(assetSet)
|
|
183
|
+
.filter((asset) => allowedAssets.has(asset.toLowerCase()))
|
|
184
|
+
.sort((a, b) => {
|
|
185
|
+
// Sort in specific order: ETH, BTC, STRK, USDT, USDC
|
|
186
|
+
const order = ["eth", "btc", "strk", "usdt", "usdc"];
|
|
187
|
+
const aIndex = order.indexOf(a.toLowerCase());
|
|
188
|
+
const bIndex = order.indexOf(b.toLowerCase());
|
|
189
|
+
if (aIndex === -1 && bIndex === -1) return 0;
|
|
190
|
+
if (aIndex === -1) return 1;
|
|
191
|
+
if (bIndex === -1) return -1;
|
|
192
|
+
return aIndex - bIndex;
|
|
193
|
+
})
|
|
194
|
+
.map((id) => ({
|
|
195
|
+
id,
|
|
196
|
+
label: id.toUpperCase()
|
|
197
|
+
}));
|
|
198
|
+
|
|
199
|
+
// Extract unique protocols
|
|
200
|
+
const protocolSet = new Set<string>();
|
|
201
|
+
allMetadata.forEach((meta) => {
|
|
202
|
+
meta.protocols.forEach((protocol) => protocolSet.add(protocol));
|
|
203
|
+
});
|
|
204
|
+
const protocols: FilterOption[] = Array.from(protocolSet)
|
|
205
|
+
.sort()
|
|
206
|
+
.map((id) => ({
|
|
207
|
+
id,
|
|
208
|
+
label: id.charAt(0).toUpperCase() + id.slice(1)
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
const quickFilters: FilterOption[] = [
|
|
212
|
+
{ id: "all", label: "All Strategies" },
|
|
213
|
+
...getAllStrategyTags().map((tag) => ({ id: tag.toLowerCase().replaceAll(" ", "-"), label: tag })),
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
assets,
|
|
218
|
+
protocols,
|
|
219
|
+
quickFilters
|
|
220
|
+
};
|
|
221
|
+
}
|
|
237
222
|
|
|
238
223
|
/**
|
|
239
224
|
* Get live strategies (filter out retired)
|
|
@@ -252,51 +237,3 @@ export function getStrategiesByType(
|
|
|
252
237
|
const registry = buildStrategyRegistry();
|
|
253
238
|
return registry.filter((entry) => entry.type === type);
|
|
254
239
|
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Get strategies by filter criteria
|
|
258
|
-
*/
|
|
259
|
-
export interface StrategyFilterCriteria {
|
|
260
|
-
assets?: string[];
|
|
261
|
-
protocols?: string[];
|
|
262
|
-
categories?: string[];
|
|
263
|
-
showRetired?: boolean;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
export function getFilteredStrategies(
|
|
267
|
-
criteria: StrategyFilterCriteria,
|
|
268
|
-
): StrategyMetadata[] {
|
|
269
|
-
let allMetadata = getAllStrategyMetadata();
|
|
270
|
-
|
|
271
|
-
// Filter out retired strategies if not explicitly shown
|
|
272
|
-
if (!criteria.showRetired) {
|
|
273
|
-
allMetadata = allMetadata.filter((meta) => !meta.isRetired);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Filter by assets
|
|
277
|
-
if (criteria.assets && criteria.assets.length > 0) {
|
|
278
|
-
allMetadata = allMetadata.filter((meta) =>
|
|
279
|
-
meta.assets.some((asset) =>
|
|
280
|
-
criteria.assets!.includes(asset.toLowerCase()),
|
|
281
|
-
),
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Filter by protocols
|
|
286
|
-
if (criteria.protocols && criteria.protocols.length > 0) {
|
|
287
|
-
allMetadata = allMetadata.filter((meta) =>
|
|
288
|
-
meta.protocols.some((protocol) =>
|
|
289
|
-
criteria.protocols!.includes(protocol.toLowerCase()),
|
|
290
|
-
),
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Filter by categories
|
|
295
|
-
if (criteria.categories && criteria.categories.length > 0) {
|
|
296
|
-
allMetadata = allMetadata.filter((meta) =>
|
|
297
|
-
criteria.categories!.includes(meta.category),
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return allMetadata;
|
|
302
|
-
}
|
package/src/strategies/sensei.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { getNoRiskTags, highlightTextWithLinks, IConfig, IProtocol, IStrategyMetadata, RiskFactor, RiskType,
|
|
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, BlockIdentifier } 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";
|
|
@@ -11,6 +11,8 @@ import ERC20ABI from "@/data/erc20.abi.json";
|
|
|
11
11
|
import { AvnuWrapper } from "@/modules";
|
|
12
12
|
import { gql } from "@apollo/client";
|
|
13
13
|
import apolloClient from "@/modules/apollo-client";
|
|
14
|
+
import { VesuAdapter, VesuPools } from "./universal-adapters/vesu-adapter";
|
|
15
|
+
import { LSTAPRService } from "@/modules/lst-apr";
|
|
14
16
|
|
|
15
17
|
export interface SenseiVaultSettings {
|
|
16
18
|
mainToken: TokenInfo;
|
|
@@ -236,12 +238,111 @@ export class SenseiVault extends BaseStrategy<
|
|
|
236
238
|
return settings;
|
|
237
239
|
};
|
|
238
240
|
|
|
241
|
+
async netAPY(): Promise<number> {
|
|
242
|
+
try {
|
|
243
|
+
// Fetch Vesu pools and select the Re7 xSTRK pool
|
|
244
|
+
const { pools } = await VesuAdapter.getVesuPools();
|
|
245
|
+
const re7PoolId = VesuPools.Re7xSTRK;
|
|
246
|
+
const pool = pools.find((p: any) =>
|
|
247
|
+
ContractAddr.from(num.getHexString(p.id)).eq(re7PoolId),
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
if (!pool) {
|
|
251
|
+
logger.warn(`${SenseiVault.name}::netAPY - Re7 xSTRK pool not found`);
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const mainSymbol = this.metadata.additionalInfo.mainToken.symbol.toLowerCase(); // STRK
|
|
256
|
+
const secondarySymbol =
|
|
257
|
+
this.metadata.additionalInfo.secondaryToken.symbol.toLowerCase(); // xSTRK
|
|
258
|
+
|
|
259
|
+
const collateralAssetStats = pool.assets.find(
|
|
260
|
+
(a: any) => String(a.symbol).toLowerCase() === secondarySymbol,
|
|
261
|
+
)?.stats;
|
|
262
|
+
const debtAssetStats = pool.assets.find(
|
|
263
|
+
(a: any) => String(a.symbol).toLowerCase() === mainSymbol,
|
|
264
|
+
)?.stats;
|
|
265
|
+
|
|
266
|
+
if (!collateralAssetStats || !debtAssetStats) {
|
|
267
|
+
logger.warn(
|
|
268
|
+
`${SenseiVault.name}::netAPY - Missing collateral/debt stats on Vesu pool`,
|
|
269
|
+
);
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Base xSTRK lending APY from Vesu
|
|
274
|
+
const xstrkSupplyAPY =
|
|
275
|
+
Number(collateralAssetStats.supplyApy?.value || 0) / 1e18;
|
|
276
|
+
|
|
277
|
+
// STRK rewards APR on xSTRK collateral from Vesu
|
|
278
|
+
const strkRewardsAPR = collateralAssetStats.defiSpringSupplyApr
|
|
279
|
+
? Number(collateralAssetStats.defiSpringSupplyApr.value || 0) / 1e18
|
|
280
|
+
: 0;
|
|
281
|
+
|
|
282
|
+
// STRK borrow APY from Vesu
|
|
283
|
+
const borrowAPY =
|
|
284
|
+
Number(debtAssetStats.borrowApr?.value || 0) / 1e18;
|
|
285
|
+
|
|
286
|
+
// LST APR for xSTRK from Endur (based on underlying STRK asset)
|
|
287
|
+
const lstAPY = await LSTAPRService.getLSTAPR(
|
|
288
|
+
this.metadata.additionalInfo.mainToken.address,
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
// Collateral APY = Vesu xSTRK supply + Endur xSTRK APR + STRK rewards
|
|
292
|
+
const collateralAPY = xstrkSupplyAPY + lstAPY + strkRewardsAPR;
|
|
293
|
+
|
|
294
|
+
const feeFactor = this.metadata.additionalInfo.feeBps / 10000; // convert bps to decimal
|
|
295
|
+
const feeAdjustedColAPY =
|
|
296
|
+
collateralAPY - strkRewardsAPR * feeFactor;
|
|
297
|
+
|
|
298
|
+
// Position info (collateral & debt in USD terms)
|
|
299
|
+
const { collateralUSDValue, debtUSDValue } =
|
|
300
|
+
await this.getPositionInfo();
|
|
301
|
+
|
|
302
|
+
const collateralUSD = Number(collateralUSDValue.toFixed(6));
|
|
303
|
+
const debtUSD = Number(debtUSDValue.toFixed(6));
|
|
304
|
+
|
|
305
|
+
// Compute expected leverage using the same math as app-side strategy
|
|
306
|
+
const targetHf = this.metadata.additionalInfo.targetHfBps / 10000;
|
|
307
|
+
const xSTRKPrice = await this.getSecondaryTokenPriceRelativeToMain();
|
|
308
|
+
const denominator = targetHf * xSTRKPrice - 0.87;
|
|
309
|
+
if (denominator <= 0) {
|
|
310
|
+
logger.warn(
|
|
311
|
+
`${SenseiVault.name}::netAPY - Invalid denominator in leverage calc`,
|
|
312
|
+
);
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
const borrowedSTRK = (0.87 * xSTRKPrice) / denominator;
|
|
316
|
+
const expectedLeverage = 1 + borrowedSTRK;
|
|
317
|
+
if (!Number.isFinite(expectedLeverage) || expectedLeverage <= 0) {
|
|
318
|
+
logger.warn(
|
|
319
|
+
`${SenseiVault.name}::netAPY - Non-positive or invalid expectedLeverage`,
|
|
320
|
+
);
|
|
321
|
+
return 0;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const payoff =
|
|
325
|
+
collateralUSD * feeAdjustedColAPY - debtUSD * borrowAPY;
|
|
326
|
+
const investment = collateralUSD - debtUSD;
|
|
327
|
+
|
|
328
|
+
if (investment === 0) {
|
|
329
|
+
return 0;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const netAPY = payoff / investment;
|
|
333
|
+
return Number.isFinite(netAPY) ? netAPY : 0;
|
|
334
|
+
} catch (error) {
|
|
335
|
+
logger.error(`${SenseiVault.name}::netAPY - Error`, error);
|
|
336
|
+
return 0;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
239
340
|
/**
|
|
240
341
|
* Calculates user realized APY based on position growth accounting for deposits and withdrawals.
|
|
241
342
|
* Returns the APY as a number.
|
|
343
|
+
* Not implemented for Sensei Strategy yet.
|
|
242
344
|
*/
|
|
243
345
|
async getUserRealizedAPY(
|
|
244
|
-
userAddress: ContractAddr,
|
|
245
346
|
blockIdentifier: BlockIdentifier = "latest",
|
|
246
347
|
sinceBlocks = 600000
|
|
247
348
|
): Promise<number> {
|
|
@@ -453,9 +554,10 @@ const FAQS = [
|
|
|
453
554
|
export const SenseiStrategies: IStrategyMetadata<SenseiVaultSettings>[] =
|
|
454
555
|
[
|
|
455
556
|
{
|
|
557
|
+
id: "xstrk_sensei",
|
|
456
558
|
name: "xSTRK Sensei",
|
|
457
559
|
description: highlightTextWithLinks(
|
|
458
|
-
senseiDescription.
|
|
560
|
+
senseiDescription.replace('{{token1}}', 'STRK').replace('{{token2}}', 'xSTRK'),
|
|
459
561
|
[{
|
|
460
562
|
highlight: "Endur",
|
|
461
563
|
link: "https://endur.fi"
|
|
@@ -472,11 +574,37 @@ export const SenseiStrategies: IStrategyMetadata<SenseiVaultSettings>[] =
|
|
|
472
574
|
),
|
|
473
575
|
launchBlock: 1053811,
|
|
474
576
|
type: "Other",
|
|
577
|
+
curator: {
|
|
578
|
+
name: "Unwrap Labs",
|
|
579
|
+
logo: "https://assets.troves.fi/integrations/unwraplabs/white.png"
|
|
580
|
+
},
|
|
581
|
+
vaultType: {
|
|
582
|
+
type: VaultType.FARMING,
|
|
583
|
+
description: "this is a yield farming vault"
|
|
584
|
+
},
|
|
475
585
|
depositTokens: [
|
|
476
586
|
Global.getDefaultTokens().find((t) => t.symbol === "STRK")!
|
|
477
587
|
],
|
|
478
588
|
protocols: [endurProtocol, vesuProtocol],
|
|
479
|
-
|
|
589
|
+
settings: {
|
|
590
|
+
maxTVL: new Web3Number("1500000", 18),
|
|
591
|
+
alerts: [
|
|
592
|
+
{
|
|
593
|
+
type: "info",
|
|
594
|
+
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.",
|
|
595
|
+
tab: "all"
|
|
596
|
+
}
|
|
597
|
+
],
|
|
598
|
+
liveStatus: StrategyLiveStatus.ACTIVE,
|
|
599
|
+
isPaused: false,
|
|
600
|
+
isInMaintenance: false,
|
|
601
|
+
isAudited: false,
|
|
602
|
+
isInstantWithdrawal: true,
|
|
603
|
+
isTransactionHistDisabled: true,
|
|
604
|
+
quoteToken: Global.getDefaultTokens().find(
|
|
605
|
+
(t) => t.symbol === "STRK"
|
|
606
|
+
)!
|
|
607
|
+
},
|
|
480
608
|
risk: {
|
|
481
609
|
riskFactor: _riskFactor,
|
|
482
610
|
netRisk:
|
|
@@ -500,8 +628,7 @@ export const SenseiStrategies: IStrategyMetadata<SenseiVaultSettings>[] =
|
|
|
500
628
|
"Repeat the process to loop your position",
|
|
501
629
|
"Claim DeFi spring (STRK) rewards weekly and reinvest",
|
|
502
630
|
],
|
|
503
|
-
|
|
504
|
-
tags: [StrategyTag.SENSEI],
|
|
631
|
+
tags: [StrategyTag.LEVERED],
|
|
505
632
|
security: {
|
|
506
633
|
auditStatus: AuditStatus.AUDITED,
|
|
507
634
|
sourceCode: {
|
|
@@ -232,7 +232,8 @@ function getVesuMultiplyParams(isIncrease: boolean, params: IncreaseLeverParams
|
|
|
232
232
|
export const VesuPools = {
|
|
233
233
|
Genesis: ContractAddr.from('0x4dc4f0ca6ea4961e4c8373265bfd5317678f4fe374d76f3fd7135f57763bf28'),
|
|
234
234
|
Re7xSTRK: ContractAddr.from('0x052fb52363939c3aa848f8f4ac28f0a51379f8d1b971d8444de25fbd77d8f161'),
|
|
235
|
-
Re7xBTC: ContractAddr.from('0x3a8416bf20d036df5b1cf3447630a2e1cb04685f6b0c3a70ed7fb1473548ecf')
|
|
235
|
+
Re7xBTC: ContractAddr.from('0x3a8416bf20d036df5b1cf3447630a2e1cb04685f6b0c3a70ed7fb1473548ecf'),
|
|
236
|
+
Prime: ContractAddr.from('0x451fe483d5921a2919ddd81d0de6696669bccdacd859f72a4fba7656b97c3b5'),
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
export const extensionMap: {[key: string]: ContractAddr} = {};
|
|
@@ -587,7 +588,8 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
587
588
|
const assetConfig = isV2 ? _assetConfig : _assetConfig['0'];
|
|
588
589
|
const timeDelta = assetConfig.last_updated;
|
|
589
590
|
const lastFullUtilizationRate = assetConfig.last_full_utilization_rate;
|
|
590
|
-
const
|
|
591
|
+
const debtSharePrice = Web3Number.fromWei(assetConfig.last_rate_accumulator, 18);
|
|
592
|
+
const currentDebt = (new Web3Number((Number(assetConfig.total_nominal_debt) / 1e18).toFixed(9), asset.decimals)).multipliedBy(debtSharePrice);
|
|
591
593
|
const totalSupply = currentDebt.plus(Web3Number.fromWei(assetConfig.reserve, asset.decimals));
|
|
592
594
|
|
|
593
595
|
const ratePerSecond = BigInt(Math.round(maxBorrowAPY / 365 / 24 / 60 / 60 * Number(SCALE)));
|
|
@@ -596,7 +598,7 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
596
598
|
|
|
597
599
|
const maxDebtToHave = totalSupply.multipliedBy(Number(maxUtilisation) / 1e18);
|
|
598
600
|
logger.verbose(`${asset.symbol}::VesuAdapter::getMaxBorrowableByInterestRate currentDebt: ${currentDebt.toString()}, maxDebtToHave: ${maxDebtToHave.toString()}`);
|
|
599
|
-
return maxDebtToHave.minus(currentDebt);
|
|
601
|
+
return {maxDebtToHave: maxDebtToHave.minus(currentDebt), currentDebt: currentDebt, totalSupply: totalSupply};
|
|
600
602
|
}
|
|
601
603
|
|
|
602
604
|
async getLTVConfig(config: IConfig, blockNumber: BlockIdentifier = 'latest') {
|
|
@@ -745,7 +747,7 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
745
747
|
let pools: any[] = [];
|
|
746
748
|
try {
|
|
747
749
|
const data = await getAPIUsingHeadlessBrowser(
|
|
748
|
-
`${ENDPOINTS.
|
|
750
|
+
`${ENDPOINTS.VESU_BASE}/pools`
|
|
749
751
|
);
|
|
750
752
|
pools = data.data;
|
|
751
753
|
|
|
@@ -891,4 +893,4 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
891
893
|
}
|
|
892
894
|
throw new Error('Max utilization not found');
|
|
893
895
|
}
|
|
894
|
-
}
|
|
896
|
+
}
|