@strkfarm/sdk 1.0.17 → 1.0.19

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.
@@ -1,5 +1,5 @@
1
1
  import { ContractAddr, Web3Number } from "@/dataTypes";
2
- import { IConfig, IInvestmentFlow, IProtocol, IStrategyMetadata, RiskFactor, RiskType } from "@/interfaces";
2
+ import { FlowChartColors, IConfig, IInvestmentFlow, IProtocol, IStrategyMetadata, RiskFactor, RiskType } from "@/interfaces";
3
3
  import { Pricer } from "@/modules";
4
4
  import { CairoCustomEnum, Contract, num, uint256 } from "starknet";
5
5
  import VesuRebalanceAbi from '@/data/vesu-rebalance.abi.json';
@@ -7,6 +7,8 @@ import { Global } from "@/global";
7
7
  import { assert } from "@/utils";
8
8
  import axios from "axios";
9
9
  import { PricerBase } from "@/modules/pricerBase";
10
+ import { BaseStrategy, SingleActionAmount, SingleTokenInfo } from "./base-strategy";
11
+ import { getAPIUsingHeadlessBrowser } from "@/node/headless";
10
12
 
11
13
  interface PoolProps {
12
14
  pool_id: ContractAddr;
@@ -21,6 +23,10 @@ interface Change {
21
23
  isDeposit: boolean;
22
24
  }
23
25
 
26
+ interface VesuRebalanceSettings {
27
+ feeBps: number;
28
+ }
29
+
24
30
  interface PoolInfoFull {
25
31
  pool_id: ContractAddr;
26
32
  pool_name: string | undefined;
@@ -42,15 +48,13 @@ interface PoolInfoFull {
42
48
  * This class implements an automated rebalancing strategy for Vesu pools,
43
49
  * managing deposits and withdrawals while optimizing yield through STRK rewards.
44
50
  */
45
- export class VesuRebalance {
46
- /** Configuration object for the strategy */
47
- readonly config: IConfig;
51
+ export class VesuRebalance extends BaseStrategy<SingleTokenInfo, SingleActionAmount> {
48
52
  /** Contract address of the strategy */
49
53
  readonly address: ContractAddr;
50
54
  /** Pricer instance for token price calculations */
51
55
  readonly pricer: PricerBase;
52
56
  /** Metadata containing strategy information */
53
- readonly metadata: IStrategyMetadata;
57
+ readonly metadata: IStrategyMetadata<VesuRebalanceSettings>;
54
58
  /** Contract instance for interacting with the strategy */
55
59
  readonly contract: Contract;
56
60
  readonly BASE_WEIGHT = 10000; // 10000 bps = 100%
@@ -62,8 +66,8 @@ export class VesuRebalance {
62
66
  * @param metadata - Strategy metadata including deposit tokens and address
63
67
  * @throws {Error} If more than one deposit token is specified
64
68
  */
65
- constructor(config: IConfig, pricer: PricerBase, metadata: IStrategyMetadata) {
66
- this.config = config;
69
+ constructor(config: IConfig, pricer: PricerBase, metadata: IStrategyMetadata<VesuRebalanceSettings>) {
70
+ super(config);
67
71
  this.pricer = pricer;
68
72
 
69
73
  assert(metadata.depositTokens.length === 1, 'VesuRebalance only supports 1 deposit token');
@@ -79,12 +83,13 @@ export class VesuRebalance {
79
83
  * @param receiver - Address that will receive the strategy tokens
80
84
  * @returns Populated contract call for deposit
81
85
  */
82
- depositCall(assets: Web3Number, receiver: ContractAddr) {
86
+ depositCall(amountInfo: SingleActionAmount, receiver: ContractAddr) {
83
87
  // Technically its not erc4626 abi, but we just need approve call
84
88
  // so, its ok to use it
85
- const assetContract = new Contract(VesuRebalanceAbi, this.metadata.depositTokens[0].address, this.config.provider);
86
- const call1 = assetContract.populate('approve', [this.address.address, uint256.bnToUint256(assets.toWei())]);
87
- const call2 = this.contract.populate('deposit', [uint256.bnToUint256(assets.toWei()), receiver.address]);
89
+ assert(amountInfo.tokenInfo.address.eq(this.asset().address), 'Deposit token mismatch');
90
+ const assetContract = new Contract(VesuRebalanceAbi, this.asset().address.address, this.config.provider);
91
+ const call1 = assetContract.populate('approve', [this.address.address, uint256.bnToUint256(amountInfo.amount.toWei())]);
92
+ const call2 = this.contract.populate('deposit', [uint256.bnToUint256(amountInfo.amount.toWei()), receiver.address]);
88
93
  return [call1, call2];
89
94
  }
90
95
 
@@ -95,8 +100,8 @@ export class VesuRebalance {
95
100
  * @param owner - Address that owns the strategy tokens
96
101
  * @returns Populated contract call for withdrawal
97
102
  */
98
- withdrawCall(assets: Web3Number, receiver: ContractAddr, owner: ContractAddr) {
99
- return [this.contract.populate('withdraw', [uint256.bnToUint256(assets.toWei()), receiver.address, owner.address])];
103
+ withdrawCall(amountInfo: SingleActionAmount, receiver: ContractAddr, owner: ContractAddr) {
104
+ return [this.contract.populate('withdraw', [uint256.bnToUint256(amountInfo.amount.toWei()), receiver.address, owner.address])];
100
105
  }
101
106
 
102
107
  /**
@@ -127,6 +132,7 @@ export class VesuRebalance {
127
132
  let price = await this.pricer.getPrice(this.metadata.depositTokens[0].symbol);
128
133
  const usdValue = Number(amount.toFixed(6)) * price.price;
129
134
  return {
135
+ tokenInfo: this.asset(),
130
136
  amount,
131
137
  usdValue
132
138
  }
@@ -142,11 +148,30 @@ export class VesuRebalance {
142
148
  let price = await this.pricer.getPrice(this.metadata.depositTokens[0].symbol);
143
149
  const usdValue = Number(amount.toFixed(6)) * price.price;
144
150
  return {
151
+ tokenInfo: this.asset(),
145
152
  amount,
146
153
  usdValue
147
154
  }
148
155
  }
149
156
 
157
+ static async getAllPossibleVerifiedPools(asset: ContractAddr) {
158
+ const data = await getAPIUsingHeadlessBrowser('https://api.vesu.xyz/pools');
159
+ const verifiedPools =data.data.filter((d: any) => d.isVerified);
160
+ const pools = verifiedPools.map((p: any) => {
161
+ const hasMyAsset = p.assets.find((a: any) => asset.eqString(a.address));
162
+ if (hasMyAsset) {
163
+ return {
164
+ pool_id: ContractAddr.from(p.id),
165
+ max_weight: 10000,
166
+ v_token: ContractAddr.from(hasMyAsset.vToken.address),
167
+ name: p.name,
168
+ }
169
+ }
170
+ return null;
171
+ }).filter((p: PoolProps | null) => p !== null);
172
+ return pools;
173
+ }
174
+
150
175
  /**
151
176
  * Retrieves the list of allowed pools and their detailed information from multiple sources:
152
177
  * 1. Contract's allowed pools
@@ -217,11 +242,10 @@ export class VesuRebalance {
217
242
  let isErrorPositionsAPI = false;
218
243
  let vesuPositions: any[] = [];
219
244
  try {
220
- const res = await axios.get(`https://api.vesu.xyz/positions?walletAddress=${this.address.address}`)
221
- const data = await res.data;
245
+ const data = await getAPIUsingHeadlessBrowser(`https://api.vesu.xyz/positions?walletAddress=${this.address.address}`)
222
246
  vesuPositions = data.data;
223
247
  } catch (e) {
224
- console.error(`${VesuRebalance.name}: Error fetching pools for ${this.address.address}`, e);
248
+ console.error(`${VesuRebalance.name}: Error fetching positions for ${this.address.address}`, e);
225
249
  isErrorPositionsAPI = true;
226
250
  }
227
251
 
@@ -229,8 +253,7 @@ export class VesuRebalance {
229
253
  let isErrorPoolsAPI = false;
230
254
  let pools: any[] = [];
231
255
  try {
232
- const res = await axios.get(`https://api.vesu.xyz/pools`);
233
- const data = await res.data;
256
+ const data = await getAPIUsingHeadlessBrowser('https://api.vesu.xyz/pools');
234
257
  pools = data.data;
235
258
  } catch (e) {
236
259
  console.error(`${VesuRebalance.name}: Error fetching pools for ${this.address.address}`, e);
@@ -242,13 +265,13 @@ export class VesuRebalance {
242
265
  const info = allowedPools.map(async (p) => {
243
266
  const vesuPosition = vesuPositions.find((d: any) => d.pool.id.toString() === num.getDecimalString(p.pool_id.address.toString()));
244
267
  const pool = pools.find((d: any) => d.id == num.getDecimalString(p.pool_id.address));
245
- const assetInfo = pool?.assets.find((d: any) => ContractAddr.from(this.asset().address).eqString(d.address));
268
+ const assetInfo = pool?.assets.find((d: any) => this.asset().address.eqString(d.address));
246
269
  let vTokenContract = new Contract(VesuRebalanceAbi, p.v_token.address, this.config.provider);
247
270
  const bal = await vTokenContract.balanceOf(this.address.address);
248
271
  const assets = await vTokenContract.convert_to_assets(uint256.bnToUint256(bal.toString()));
249
272
  const item = {
250
273
  pool_id: p.pool_id,
251
- pool_name: vesuPosition?.pool.name,
274
+ pool_name: pool.name,
252
275
  max_weight: p.max_weight,
253
276
  current_weight: isErrorPositionsAPI || !vesuPosition ? 0 : Number(Web3Number.fromWei(vesuPosition.collateral.value, this.decimals()).dividedBy(totalAssets.toString()).toFixed(6)),
254
277
  v_token: p.v_token,
@@ -297,7 +320,7 @@ export class VesuRebalance {
297
320
  return acc + (curr.APY.netApy * weight);
298
321
  }, 0);
299
322
 
300
- return weightedApy;
323
+ return weightedApy * (1 - (this.metadata.additionalInfo.feeBps / 10000));
301
324
  }
302
325
 
303
326
  /**
@@ -418,24 +441,26 @@ export class VesuRebalance {
418
441
  const netYield = this.netAPYGivenPools(pools);
419
442
 
420
443
  const baseFlow: IInvestmentFlow = {
421
- title: "Deposit $1000",
422
- subItems: [`Net yield: ${(netYield * 100).toFixed(2)}%`],
444
+ title: "Your Deposit",
445
+ subItems: [{key: `Net yield`, value: `${(netYield * 100).toFixed(2)}%`}],
423
446
  linkedFlows: [],
447
+ style: {backgroundColor: FlowChartColors.Purple.valueOf()},
424
448
  };
425
449
 
426
- pools.forEach((p) => {
427
- if (p.amount.eq(0)) return;
450
+ let _pools = [...pools];
451
+ _pools = _pools.sort((a, b) => Number(b.amount.toString()) - Number(a.amount.toString()));
452
+ _pools.forEach((p) => {
428
453
  const flow: IInvestmentFlow = {
429
- title: `${p.pool_name} - $${(p.current_weight * 1000).toFixed(2)}`,
454
+ title: `Pool name: ${p.pool_name}`,
430
455
  subItems: [
431
- `APY: ${(p.APY.netApy * 100).toFixed(2)}%`,
432
- `Weight: ${(p.current_weight * 100).toFixed(2)}% / ${(p.max_weight * 100).toFixed(2)}%`,
456
+ {key: `APY`, value: `${(p.APY.netApy * 100).toFixed(2)}%`},
457
+ {key: 'Weight', value: `${(p.current_weight * 100).toFixed(2)} / ${(p.max_weight * 100).toFixed(2)}%`}
433
458
  ],
434
459
  linkedFlows: [],
460
+ style: p.amount.greaterThan(0) ? {backgroundColor: FlowChartColors.Blue.valueOf()} : {color: 'gray'},
435
461
  };
436
462
  baseFlow.linkedFlows.push(flow);
437
463
  });
438
-
439
464
  return [baseFlow];
440
465
  }
441
466
  }
@@ -445,22 +470,85 @@ const _protocol: IProtocol = {name: 'Vesu', logo: 'https://static-assets-8zct.on
445
470
  // need to fine tune better
446
471
  const _riskFactor: RiskFactor[] = [
447
472
  {type: RiskType.SMART_CONTRACT_RISK, value: 0.5, weight: 25},
448
- {type: RiskType.TECHNICAL_RISK, value: 0.5, weight: 25},
449
473
  {type: RiskType.COUNTERPARTY_RISK, value: 1, weight: 50},
474
+ {type: RiskType.ORACLE_RISK, value: 0.5, weight: 25},
450
475
  ]
451
476
  /**
452
477
  * Represents the Vesu Rebalance Strategies.
453
478
  */
454
- export const VesuRebalanceStrategies: IStrategyMetadata[] = [{
455
- name: 'Vesu STRK',
479
+ export const VesuRebalanceStrategies: IStrategyMetadata<VesuRebalanceSettings>[] = [{
480
+ name: 'Vesu Fusion STRK',
456
481
  description: _description.replace('{{TOKEN}}', 'STRK'),
457
- address: ContractAddr.from('0xeeb729d554ae486387147b13a9c8871bc7991d454e8b5ff570d4bf94de71e1'),
482
+ address: ContractAddr.from('0x778007f8136a5b827325d21613803e796bda4d676fbe1e34aeab0b2a2ec027f'),
458
483
  type: 'ERC4626',
459
484
  depositTokens: [Global.getDefaultTokens().find(t => t.symbol === 'STRK')!],
460
485
  protocols: [_protocol],
461
486
  maxTVL: Web3Number.fromWei('0', 18),
462
487
  risk: {
463
488
  riskFactor: _riskFactor,
464
- netRisk: _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / 100,
465
- }
489
+ netRisk: _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
490
+ },
491
+ additionalInfo: {
492
+ feeBps: 1000,
493
+ },
494
+ }, {
495
+ name: 'Vesu Fusion ETH',
496
+ description: _description.replace('{{TOKEN}}', 'ETH'),
497
+ address: ContractAddr.from('0x26ea414fdf74ace1df5bc5ac72cbac670d438ef19b31edf9d59f98718fc0ab2'),
498
+ type: 'ERC4626',
499
+ depositTokens: [Global.getDefaultTokens().find(t => t.symbol === 'ETH')!],
500
+ protocols: [_protocol],
501
+ maxTVL: Web3Number.fromWei('0', 18),
502
+ risk: {
503
+ riskFactor: _riskFactor,
504
+ netRisk: _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
505
+ },
506
+ additionalInfo: {
507
+ feeBps: 1000,
508
+ },
509
+ }, {
510
+ name: 'Vesu Fusion USDC',
511
+ description: _description.replace('{{TOKEN}}', 'USDC'),
512
+ address: ContractAddr.from('0x3a69adeb993cddb266962d9c995e3d0641dab627df22b825fa31bda460c3c14'),
513
+ type: 'ERC4626',
514
+ depositTokens: [Global.getDefaultTokens().find(t => t.symbol === 'USDC')!],
515
+ protocols: [_protocol],
516
+ maxTVL: Web3Number.fromWei('0', 6),
517
+ risk: {
518
+ riskFactor: _riskFactor,
519
+ netRisk: _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
520
+ },
521
+ additionalInfo: {
522
+ feeBps: 1000,
523
+ },
524
+ // }, {
525
+ // name: 'Vesu Fusion USDT',
526
+ // description: _description.replace('{{TOKEN}}', 'USDT'),
527
+ // address: ContractAddr.from('0x778007f8136a5b827325d21613803e796bda4d676fbe1e34aeab0b2a2ec027f'),
528
+ // type: 'ERC4626',
529
+ // depositTokens: [Global.getDefaultTokens().find(t => t.symbol === 'USDT')!],
530
+ // protocols: [_protocol],
531
+ // maxTVL: Web3Number.fromWei('0', 6),
532
+ // risk: {
533
+ // riskFactor: _riskFactor,
534
+ // netRisk: _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
535
+ // },
536
+ // additionalInfo: {
537
+ // feeBps: 1000,
538
+ // },
539
+ // }, {
540
+ // name: 'Vesu Fusion WBTC',
541
+ // description: _description.replace('{{TOKEN}}', 'WBTC'),
542
+ // address: ContractAddr.from('0x778007f8136a5b827325d21613803e796bda4d676fbe1e34aeab0b2a2ec027f'),
543
+ // type: 'ERC4626',
544
+ // depositTokens: [Global.getDefaultTokens().find(t => t.symbol === 'WBTC')!],
545
+ // protocols: [_protocol],
546
+ // maxTVL: Web3Number.fromWei('0', 8),
547
+ // risk: {
548
+ // riskFactor: _riskFactor,
549
+ // netRisk: _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
550
+ // },
551
+ // additionalInfo: {
552
+ // feeBps: 1000,
553
+ // },
466
554
  }]