@strkfarm/sdk 1.1.16 → 1.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strkfarm/sdk",
3
- "version": "1.1.16",
3
+ "version": "1.1.19",
4
4
  "description": "STRKFarm TS SDK (Meant for our internal use, but feel free to use it)",
5
5
  "typings": "dist/index.d.ts",
6
6
  "types": "dist/index.d.ts",
@@ -62,6 +62,7 @@
62
62
  "@noble/curves": "^1.0.0",
63
63
  "@noble/hashes": "^2.0.0",
64
64
  "@scure/starknet": "^2.0.0",
65
+ "@strkfarm/sdk": "link:",
65
66
  "bignumber.js": "4.0.4",
66
67
  "browser-assert": "^1.2.1",
67
68
  "chalk": "^4.1.2",
@@ -10,27 +10,42 @@ export class _Web3Number<T extends _Web3Number<T>> extends BigNumber {
10
10
  }
11
11
 
12
12
  toWei() {
13
- return this.mul(10 ** this.decimals).toFixed(0);
13
+ return super.mul(10 ** this.decimals).toFixed(0);
14
14
  }
15
15
 
16
16
  multipliedBy(value: string | number | T): T {
17
17
  const _value = this.getStandardString(value);
18
- return this.construct(this.mul(_value).toString(), this.decimals);
18
+ return this.construct(super.mul(_value).toString(), this.decimals);
19
19
  }
20
20
 
21
21
  dividedBy(value: string | number | T): T {
22
22
  const _value = this.getStandardString(value);
23
- return this.construct(this.div(_value).toString(), this.decimals);
23
+ return this.construct(super.dividedBy(_value).toString(), this.decimals);
24
24
  }
25
25
 
26
26
  plus(value: string | number | T): T {
27
- const _value = this.getStandardString(value);
28
- return this.construct(this.add(_value).toString(), this.decimals);
27
+ // Convert to raw number/string to avoid calling overridden methods
28
+ const rawValue = this.getStandardString(value);
29
+
30
+ // Create a fresh BigNumber instance to do the math
31
+ const thisBN = new BigNumber(this.toString());
32
+ const result = thisBN.plus(rawValue);
33
+
34
+ return this.construct(result.toString(), this.decimals);
35
+ // const _value = this.getStandardString(value);
36
+ // return this.construct(super.plus(_value).toString(), this.decimals);
29
37
  }
30
38
 
31
39
  minus(n: number | string | T, base?: number): T {
32
- const _value = this.getStandardString(n);
33
- return this.construct(super.minus(_value, base).toString(), this.decimals);
40
+ const rawValue = this.getStandardString(n);
41
+
42
+ // Create a fresh BigNumber instance to do the math
43
+ const thisBN = new BigNumber(this.toString());
44
+ const result = thisBN.minus(rawValue, base);
45
+
46
+ return this.construct(result.toString(), this.decimals);
47
+ // const _value = this.getStandardString(n);
48
+ // return this.construct(super.minus(_value, base).toString(), this.decimals);
34
49
  }
35
50
 
36
51
  protected construct(value: string | number, decimals: number): T {
@@ -56,10 +71,15 @@ export class _Web3Number<T extends _Web3Number<T>> extends BigNumber {
56
71
  }
57
72
 
58
73
  private getStandardString(value: string | number | T): string {
59
- if (typeof value == "string") {
74
+ if (typeof value === "string") {
60
75
  return value;
61
76
  }
62
- return value.toFixed(this.maxToFixedDecimals());
77
+ if (typeof value === "number") {
78
+ return value.toString();
79
+ }
80
+ // For _Web3Number instances, use the parent BigNumber's toString
81
+ // to avoid triggering our overridden methods
82
+ return BigNumber.prototype.toString.call(value);
63
83
  }
64
84
 
65
85
  minimum(value: string | number | T): T {
@@ -72,13 +92,11 @@ export class _Web3Number<T extends _Web3Number<T>> extends BigNumber {
72
92
  maximum(value: string | number | T): T {
73
93
  const _value = new BigNumber(value);
74
94
  const _valueMe = new BigNumber(this.toString());
75
- console.warn(`maximum: _value: ${_value.toString()}, _valueMe: ${_valueMe.toString()}`);
76
95
  const answer = _value.greaterThanOrEqualTo(_valueMe) ? _value : _valueMe;
77
96
  return this.construct(answer.toString(), this.decimals);
78
97
  }
79
98
 
80
99
  abs(): T {
81
- console.warn(`abs: this: ${this}`);
82
100
  return this.construct(Math.abs(this.toNumber()).toFixed(12), this.decimals);
83
101
  }
84
102
 
@@ -42,7 +42,7 @@ export class PricerFromApi extends PricerBase {
42
42
 
43
43
  async getPriceFromMyAPI(tokenSymbol: string) {
44
44
  logger.verbose(`getPrice from redis: ${tokenSymbol}`);
45
- const endpoint = 'https://cache-server-t2me.onrender.com'
45
+ const endpoint = 'https://proxy.api.troves.fi'
46
46
  const url = `${endpoint}/api/price/${tokenSymbol}`;
47
47
  const priceInfoRes = await fetch(url);
48
48
  const priceInfo = await priceInfoRes.json();
@@ -147,14 +147,14 @@ export class Pricer extends PricerBase {
147
147
  logger.verbose(`Fetching price of ${token.symbol} using ${methodToUse}`);
148
148
  switch (methodToUse) {
149
149
  case 'Coinbase':
150
- try {
151
- const result = await this._getPriceCoinbase(token);
152
- this.methodToUse[token.symbol] = 'Coinbase';
153
- return result;
154
- } catch (error: any) {
155
- console.warn(`Coinbase: price err: message [${token.symbol}]: `, error.message);
156
- // do nothing, try next
157
- }
150
+ // try {
151
+ // const result = await this._getPriceCoinbase(token);
152
+ // this.methodToUse[token.symbol] = 'Coinbase';
153
+ // return result;
154
+ // } catch (error: any) {
155
+ // console.warn(`Coinbase: price err: message [${token.symbol}]: `, error.message);
156
+ // // do nothing, try next
157
+ // }
158
158
  case 'Coinmarketcap':
159
159
  try {
160
160
  const result = await this._getPriceCoinMarketCap(token);
@@ -1,5 +1,5 @@
1
1
  import { ContractAddr, Web3Number } from "@/dataTypes";
2
- import { IConfig, TokenInfo } from "@/interfaces";
2
+ import { IConfig, TokenInfo, VaultPosition } from "@/interfaces";
3
3
  import { CacheClass } from "@/utils/cacheClass";
4
4
  import { Call } from "starknet";
5
5
 
@@ -51,4 +51,8 @@ export class BaseStrategy<TVLInfo, ActionInfo> extends CacheClass {
51
51
  async withdrawCall(amountInfo: ActionInfo, receiver: ContractAddr, owner: ContractAddr): Promise<Call[]> {
52
52
  throw new Error("Not implemented");
53
53
  }
54
+
55
+ async getVaultPositions(): Promise<VaultPosition[]> {
56
+ throw new Error("Not implemented");
57
+ }
54
58
  }
@@ -7,5 +7,5 @@ export const COMMON_CONTRACTS = [{
7
7
  }];
8
8
 
9
9
  export const ENDPOINTS = {
10
- VESU_BASE: "https://cache-server-t2me.onrender.com/vesu"
10
+ VESU_BASE: "https://proxy.api.troves.fi/vesu-staging"
11
11
  }
@@ -11,6 +11,7 @@ import {
11
11
  RiskFactor,
12
12
  RiskType,
13
13
  TokenInfo,
14
+ VaultPosition,
14
15
  } from "@/interfaces";
15
16
  import { PricerBase } from "@/modules/pricerBase";
16
17
  import { assert } from "@/utils";
@@ -634,6 +635,50 @@ export class EkuboCLVault extends BaseStrategy<
634
635
  return this.tickToi129(tick);
635
636
  }
636
637
 
638
+ async getVaultPositions(): Promise<VaultPosition[]> {
639
+ const tvlInfo = await this.getTVL();
640
+ const fees = await this.getUncollectedFees();
641
+ const unusedBalance = await this.unusedBalances();
642
+ return [
643
+ {
644
+ amount: tvlInfo.token0.amount,
645
+ usdValue: tvlInfo.token0.usdValue,
646
+ token: tvlInfo.token0.tokenInfo,
647
+ remarks: `Liquidity in Ekubo`
648
+ },
649
+ {
650
+ amount: fees.token0.amount,
651
+ usdValue: fees.token0.usdValue,
652
+ token: fees.token0.tokenInfo,
653
+ remarks: "Uncollected Fees in Ekubo"
654
+ },
655
+ {
656
+ amount: unusedBalance.token0.amount,
657
+ usdValue: unusedBalance.token0.usdValue,
658
+ token: unusedBalance.token0.tokenInfo,
659
+ remarks: "Unused Balance in the Vault"
660
+ },
661
+ {
662
+ amount: tvlInfo.token1.amount,
663
+ usdValue: tvlInfo.token1.usdValue,
664
+ token: tvlInfo.token1.tokenInfo,
665
+ remarks: "Liquidity in Ekubo"
666
+ },
667
+ {
668
+ amount: fees.token1.amount,
669
+ usdValue: fees.token1.usdValue,
670
+ token: fees.token1.tokenInfo,
671
+ remarks: "Uncollected Fees in Ekubo"
672
+ },
673
+ {
674
+ amount: unusedBalance.token1.amount,
675
+ usdValue: unusedBalance.token1.usdValue,
676
+ token: unusedBalance.token1.tokenInfo,
677
+ remarks: "Unused Balance in the Vault"
678
+ }
679
+ ];
680
+ }
681
+
637
682
  async getPoolKey(
638
683
  blockIdentifier: BlockIdentifier = "latest"
639
684
  ): Promise<EkuboPoolKey> {
@@ -3,10 +3,11 @@ import { ContractAddr } from "@/dataTypes";
3
3
  // Zellic audited
4
4
  export const SIMPLE_SANITIZER = ContractAddr.from('0x5a2e3ceb3da368b983a8717898427ab7b6daf04014b70f321e777f9aad940b4');
5
5
  // Without flashloan options
6
- export const SIMPLE_SANITIZER_V2 = ContractAddr.from('0x5643d54da70a471cd2b6fa37f52ea7a13cc3f3910689a839f8490a663d2208a');
6
+ export const SIMPLE_SANITIZER_V2 = ContractAddr.from('0x7b6f98311af8aa425278570e62abf523e6462eaa01a38c1feab9b2f416492e2');
7
7
  export const PRICE_ROUTER = ContractAddr.from('0x05e83Fa38D791d2dba8E6f487758A9687FfEe191A6Cf8a6c5761ab0a110DB837');
8
8
  export const AVNU_MIDDLEWARE = ContractAddr.from('0x4a7972ed3f5d1e74a6d6c4a8f467666953d081c8f2270390cc169d50d17cb0d');
9
9
 
10
+ export const VESU_SINGLETON = ContractAddr.from('0x000d8d6dfec4d33bfb6895de9f3852143a17c6f92fd2a21da3d6924d34870160');
10
11
  export function toBigInt(value: string | number): bigint {
11
12
  if (typeof value === 'string') {
12
13
  return BigInt(value);
@@ -1,10 +1,10 @@
1
1
  import { LeafData, logger } from "@/utils"
2
2
  import { CairoCustomEnum, Contract, hash, num, RpcProvider, shortString, uint256, Uint256 } from "starknet";
3
- import { SIMPLE_SANITIZER, SIMPLE_SANITIZER_V2, toBigInt } from "./adapter-utils";
3
+ import { SIMPLE_SANITIZER, SIMPLE_SANITIZER_V2, toBigInt, VESU_SINGLETON } from "./adapter-utils";
4
4
  import { ContractAddr, Web3Number } from "@/dataTypes";
5
5
  import { AdapterLeafType, BaseAdapter, GenerateCallFn, LeafAdapterFn, ManageCall } from "./baseAdapter";
6
6
  import VesuSingletonAbi from '../../data/vesu-singleton.abi.json';
7
- import { IConfig, TokenInfo, VaultPosition } from "@/interfaces";
7
+ import { getMainnetConfig, IConfig, TokenInfo, VaultPosition } from "@/interfaces";
8
8
  import { PricerBase } from "@/modules/pricerBase";
9
9
  import VesuPoolIDs from "@/data/vesu_pools.json";
10
10
  import { getAPIUsingHeadlessBrowser } from "@/node/headless";
@@ -13,6 +13,7 @@ import { VESU_REWARDS_CONTRACT } from "@/modules/harvests";
13
13
  import { ENDPOINTS } from "../constants";
14
14
  import VesuMultiplyAbi from '@/data/vesu-multiple.abi.json';
15
15
  import { EkuboPoolKey } from "../ekubo-cl-vault";
16
+ import VesuPoolV2Abi from '@/data/vesu-pool-v2.abi.json';
16
17
 
17
18
  interface VesuPoolsInfo { pools: any[]; isErrorPoolsAPI: boolean };
18
19
 
@@ -212,12 +213,20 @@ function getVesuMultiplyParams(isIncrease: boolean, params: IncreaseLeverParams
212
213
 
213
214
  export const VesuPools = {
214
215
  Genesis: ContractAddr.from('0x4dc4f0ca6ea4961e4c8373265bfd5317678f4fe374d76f3fd7135f57763bf28'),
215
- Re7xSTRK: ContractAddr.from('0x052fb52363939c3aa848f8f4ac28f0a51379f8d1b971d8444de25fbd77d8f161')
216
+ Re7xSTRK: ContractAddr.from('0x052fb52363939c3aa848f8f4ac28f0a51379f8d1b971d8444de25fbd77d8f161'),
217
+ Re7xBTC: ContractAddr.from('0x3a8416bf20d036df5b1cf3447630a2e1cb04685f6b0c3a70ed7fb1473548ecf')
218
+ }
219
+
220
+ export function getVesuSingletonAddress(vesuPool: ContractAddr) {
221
+ if (vesuPool.eq(VesuPools.Genesis) ||
222
+ vesuPool.eq(VesuPools.Re7xSTRK)) {
223
+ return {addr: VESU_SINGLETON, isV2: false};
224
+ }
225
+ return {addr: vesuPool, isV2: true}; // Vesu v2
216
226
  }
217
227
 
218
228
  export class VesuAdapter extends BaseAdapter {
219
- VESU_SINGLETON = ContractAddr.from("0x000d8d6dfec4d33bfb6895de9f3852143a17c6f92fd2a21da3d6924d34870160");
220
- VESU_MULTIPLY = ContractAddr.from('0x3630f1f8e5b8f5c4c4ae9b6620f8a570ae55cddebc0276c37550e7c118edf67');
229
+ VESU_MULTIPLY = ContractAddr.from('0x027fef272d0a9a3844767c851a64b36fe4f0115141d81134baade95d2b27b781');
221
230
  config: VesuAdapterConfig;
222
231
  networkConfig: IConfig | undefined;
223
232
  pricer: PricerBase | undefined;
@@ -239,7 +248,7 @@ export class VesuAdapter extends BaseAdapter {
239
248
  ];
240
249
  const output = this.constructSimpleLeafData({
241
250
  id: this.config.id,
242
- target: this.VESU_SINGLETON,
251
+ target: getVesuSingletonAddress(this.config.poolId).addr,
243
252
  method: 'modify_position',
244
253
  packedArguments
245
254
  });
@@ -299,9 +308,15 @@ export class VesuAdapter extends BaseAdapter {
299
308
  }
300
309
  };
301
310
  logger.verbose(`VesuAdapter::ConstructingModify::Debt::${JSON.stringify(_debt)}`)
302
- const singletonContract = new Contract({abi: VesuSingletonAbi, address: this.VESU_SINGLETON.toString(), providerOrAccount: new RpcProvider({nodeUrl: ''})});
303
- const call = singletonContract.populate('modify_position', {
304
- params: {
311
+ const { contract, isV2 } = this.getVesuSingletonContract(getMainnetConfig(), this.config.poolId);
312
+ const call = contract.populate('modify_position', {
313
+ params: isV2 ? {
314
+ collateral_asset: this.config.collateral.address.toBigInt(),
315
+ debt_asset: this.config.debt.address.toBigInt(),
316
+ user: this.config.vaultAllocator.toBigInt(),
317
+ collateral: _collateral,
318
+ debt: _debt,
319
+ } : {
305
320
  pool_id: this.config.poolId.toBigInt(),
306
321
  collateral_asset: this.config.collateral.address.toBigInt(),
307
322
  debt_asset: this.config.debt.address.toBigInt(),
@@ -314,7 +329,7 @@ export class VesuAdapter extends BaseAdapter {
314
329
  return {
315
330
  sanitizer: SIMPLE_SANITIZER,
316
331
  call: {
317
- contractAddress: this.VESU_SINGLETON,
332
+ contractAddress: ContractAddr.from(contract.address),
318
333
  selector: hash.getSelectorFromName('modify_position'),
319
334
  calldata: [
320
335
  ...call.calldata as bigint[]
@@ -372,12 +387,11 @@ export class VesuAdapter extends BaseAdapter {
372
387
  getVesuModifyDelegationAdapter = (id: string): LeafAdapterFn<VesuModifyDelegationCallParams> => {
373
388
  return () => {
374
389
  const packedArguments: bigint[] = [
375
- toBigInt(this.config.poolId.toString()), // pool id
376
390
  toBigInt(this.VESU_MULTIPLY.toString()), // vault allocator
377
391
  ];
378
392
  const output = this.constructSimpleLeafData({
379
393
  id: id,
380
- target: this.VESU_SINGLETON,
394
+ target: getVesuSingletonAddress(this.config.poolId).addr,
381
395
  method: 'modify_delegation',
382
396
  packedArguments
383
397
  }, SIMPLE_SANITIZER_V2);
@@ -387,8 +401,12 @@ export class VesuAdapter extends BaseAdapter {
387
401
  }
388
402
 
389
403
  getVesuModifyDelegationCall = (params: VesuModifyDelegationCallParams): ManageCall => {
390
- const singletonContract = new Contract({abi: VesuSingletonAbi, address: this.VESU_SINGLETON.toString(), providerOrAccount: new RpcProvider({nodeUrl: ''})});
391
- const call = singletonContract.populate('modify_delegation', {
404
+ const VESU_SINGLETON = getVesuSingletonAddress(this.config.poolId).addr;
405
+ const { contract, isV2 } = this.getVesuSingletonContract(getMainnetConfig(), this.config.poolId);
406
+ const call = contract.populate('modify_delegation', isV2 ? {
407
+ delegatee: this.VESU_MULTIPLY.toBigInt(),
408
+ delegation: params.delegation,
409
+ } : {
392
410
  pool_id: this.config.poolId.toBigInt(),
393
411
  delegatee: this.VESU_MULTIPLY.toBigInt(),
394
412
  delegation: params.delegation,
@@ -396,7 +414,7 @@ export class VesuAdapter extends BaseAdapter {
396
414
  return {
397
415
  sanitizer: SIMPLE_SANITIZER_V2,
398
416
  call: {
399
- contractAddress: this.VESU_SINGLETON,
417
+ contractAddress: VESU_SINGLETON,
400
418
  selector: hash.getSelectorFromName('modify_delegation'),
401
419
  calldata: [
402
420
  ...call.calldata as bigint[]
@@ -458,8 +476,13 @@ export class VesuAdapter extends BaseAdapter {
458
476
  throw new Error(`Unknown VesuAmountDenomination: ${denomination}`);
459
477
  }
460
478
 
461
- getVesuSingletonContract(config: IConfig) {
462
- return new Contract({abi: VesuSingletonAbi, address: this.VESU_SINGLETON.address, providerOrAccount: config.provider});
479
+ getVesuSingletonContract(config: IConfig, poolId: ContractAddr) {
480
+ const { addr: VESU_SINGLETON, isV2 } = getVesuSingletonAddress(poolId);
481
+ const ABI = isV2 ? VesuPoolV2Abi : VesuSingletonAbi;
482
+ return {
483
+ contract: new Contract({abi: ABI, address: VESU_SINGLETON.address, providerOrAccount: config.provider}),
484
+ isV2
485
+ }
463
486
  }
464
487
 
465
488
  async getLTVConfig(config: IConfig) {
@@ -468,9 +491,16 @@ export class VesuAdapter extends BaseAdapter {
468
491
  if (cacheData) {
469
492
  return cacheData as number;
470
493
  }
471
- const output: any = await this.getVesuSingletonContract(config)
472
- .call('ltv_config', [this.config.poolId.address, this.config.collateral.address.address, this.config.debt.address.address])
473
- this.setCache(CACHE_KEY, Number(output.max_ltv) / 1e18, 300000); // ttl: 5min
494
+ const { contract, isV2 } = await this.getVesuSingletonContract(config, this.config.poolId);
495
+ let ltv = 0;
496
+ if (isV2) {
497
+ const output: any = await contract.call('pair_config', [this.config.collateral.address.address, this.config.debt.address.address]);
498
+ ltv = Number(output.max_ltv) / 1e18;
499
+ } else {
500
+ const output: any = await contract.call('ltv_config', [this.config.poolId.address, this.config.collateral.address.address, this.config.debt.address.address]);
501
+ ltv = Number(output.max_ltv) / 1e18;
502
+ }
503
+ this.setCache(CACHE_KEY, ltv, 300000); // ttl: 5min
474
504
  return this.getCache<number>(CACHE_KEY) as number;
475
505
  }
476
506
 
@@ -484,9 +514,11 @@ export class VesuAdapter extends BaseAdapter {
484
514
  if (cacheData) {
485
515
  return cacheData;
486
516
  }
487
- const output: any = await this.getVesuSingletonContract(config)
488
- .call('position_unsafe', [
489
- this.config.poolId.address,
517
+
518
+ const { contract, isV2} = this.getVesuSingletonContract(config, this.config.poolId);
519
+ const output: any = await contract
520
+ .call(isV2 ? 'position' : 'position_unsafe', [...(isV2 ?
521
+ []: [this.config.poolId.address]), // exclude pool id in v2
490
522
  this.config.collateral.address.address,
491
523
  this.config.debt.address.address,
492
524
  this.config.vaultAllocator.address
@@ -494,7 +526,8 @@ export class VesuAdapter extends BaseAdapter {
494
526
 
495
527
  const token1Price = await this.pricer.getPrice(this.config.collateral.symbol);
496
528
  const token2Price = await this.pricer.getPrice(this.config.debt.symbol);
497
-
529
+ logger.verbose(`VesuAdapter::getPositions token1Price: ${token1Price.price}, token2Price: ${token2Price.price}`);
530
+
498
531
  const collateralAmount = Web3Number.fromWei(output['1'].toString(), this.config.collateral.decimals);
499
532
  const debtAmount = Web3Number.fromWei(output['2'].toString(), this.config.debt.decimals);
500
533
  const value = [{
@@ -522,9 +555,11 @@ export class VesuAdapter extends BaseAdapter {
522
555
  if (cacheData) {
523
556
  return cacheData;
524
557
  }
525
- const output: any = await this.getVesuSingletonContract(config)
526
- .call('check_collateralization_unsafe', [
527
- this.config.poolId.address,
558
+ const { contract, isV2 } = this.getVesuSingletonContract(config, this.config.poolId);
559
+ const output: any = await contract
560
+ .call(isV2 ? 'check_collateralization' : 'check_collateralization_unsafe', [
561
+ ...(isV2 ?
562
+ []: [this.config.poolId.address]), // exclude pool id in v2
528
563
  this.config.collateral.address.address,
529
564
  this.config.debt.address.address,
530
565
  this.config.vaultAllocator.address
@@ -598,7 +633,7 @@ export class VesuAdapter extends BaseAdapter {
598
633
 
599
634
  // Vesu API is unstable sometimes, some Pools may be missing sometimes
600
635
  for (const pool of VesuPoolIDs.data) {
601
- const found = pools.find((d: any) => d.id === pool.id);
636
+ const found = pools.find((d: any) => ContractAddr.from(d.id).eqString(pool.id));
602
637
  if (!found) {
603
638
  logger.verbose(`VesuRebalance: pools: ${JSON.stringify(pools)}`);
604
639
  logger.verbose(
@@ -17,7 +17,7 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<UniversalS
17
17
 
18
18
  private quoteAmountToFetchPrice = new Web3Number(1, 18);
19
19
 
20
- constructor(config: IConfig, pricer: PricerLST, metadata: IStrategyMetadata<UniversalStrategySettings>) {
20
+ constructor(config: IConfig, pricer: PricerBase, metadata: IStrategyMetadata<UniversalStrategySettings>) {
21
21
  super(config, pricer, metadata);
22
22
 
23
23
  const STRKToken = Global.getDefaultTokens().find(token => token.symbol === 'STRK')!;
@@ -30,8 +30,17 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<UniversalS
30
30
  }
31
31
  }
32
32
 
33
+ asset() {
34
+ const vesuAdapter1 = this.getAdapter(UNIVERSAL_ADAPTERS.VESU_LEG1) as VesuAdapter;
35
+ return vesuAdapter1.config.collateral;
36
+ }
37
+
38
+ getTag() {
39
+ return `${UniversalLstMultiplierStrategy.name}:${this.metadata.name}`;
40
+ }
41
+
33
42
  // only one leg is used
34
- // todo support lending assets of underlying as well
43
+ // todo support lending assets of underlying as well (like if xSTRK looping is not viable, simply supply STRK)
35
44
  getVesuAdapters() {
36
45
  const vesuAdapter1 = this.getAdapter(UNIVERSAL_ADAPTERS.VESU_LEG1) as VesuAdapter;
37
46
  vesuAdapter1.pricer = this.pricer;
@@ -42,9 +51,13 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<UniversalS
42
51
 
43
52
  // not applicable for this strategy
44
53
  // No rewards on collateral or borrowing of LST assets
45
- // protected async getRewardsAUM(prevAum: Web3Number): Promise<Web3Number> {
46
- // return Web3Number.fromWei("0", this.asset().decimals);
47
- // }
54
+ protected async getRewardsAUM(prevAum: Web3Number): Promise<Web3Number> {
55
+ const lstToken = this.asset();
56
+ if (lstToken.symbol === 'xSTRK') {
57
+ return super.getRewardsAUM(prevAum);
58
+ }
59
+ return Web3Number.fromWei("0", lstToken.decimals);
60
+ }
48
61
 
49
62
  async getLSTDexPrice() {
50
63
  const ekuboQuoter = new EkuboQuoter(this.config);
@@ -74,6 +87,7 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<UniversalS
74
87
  }) {
75
88
  const [vesuAdapter1] = this.getVesuAdapters();
76
89
  const legLTV = await vesuAdapter1.getLTVConfig(this.config);
90
+ logger.verbose(`${this.getTag()}::getVesuMultiplyCall legLTV: ${legLTV}`);
77
91
 
78
92
  const existingPositions = await vesuAdapter1.getPositions(this.config);
79
93
  const collateralisation = await vesuAdapter1.getCollateralization(this.config);
@@ -103,8 +117,11 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<UniversalS
103
117
  .multipliedBy(params.isDeposit ? 1 : -1)
104
118
  logger.verbose(`${this.getTag()}::getVesuMultiplyCall addedCollateral: ${addedCollateral}`);
105
119
  const numeratorPart1 = (existingCollateralInfo.amount.plus((addedCollateral))).multipliedBy(collateralPrice).multipliedBy(legLTV);
120
+ logger.verbose(`${this.getTag()}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}`);
106
121
  const numeratorPart2 = existingDebtInfo.amount.multipliedBy(debtPrice).multipliedBy(this.metadata.additionalInfo.targetHealthFactor);
122
+ logger.verbose(`${this.getTag()}::getVesuMultiplyCall numeratorPart2: ${numeratorPart2}`);
107
123
  const denominatorPart = this.metadata.additionalInfo.targetHealthFactor - (legLTV / dexPrice);
124
+ logger.verbose(`${this.getTag()}::getVesuMultiplyCall denominatorPart: ${denominatorPart}`);
108
125
  const x_debt_usd = numeratorPart1.minus(numeratorPart2).dividedBy(denominatorPart);
109
126
  logger.verbose(`${this.getTag()}::getVesuMultiplyCall x_debt_usd: ${x_debt_usd}`);
110
127
  logger.debug(`${this.getTag()}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}, numeratorPart2: ${numeratorPart2}, denominatorPart: ${denominatorPart}`);
@@ -159,12 +176,20 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<UniversalS
159
176
  lstDexPriceInUnderlying: number,
160
177
  isIncrease: boolean
161
178
  }): Promise<Call[]> {
179
+ logger.verbose(`${this.getTag()}::getModifyLeverCall marginAmount: ${params.marginAmount}, debtAmount: ${params.debtAmount}, lstDexPriceInUnderlying: ${params.lstDexPriceInUnderlying}, isIncrease: ${params.isIncrease}`);
162
180
  assert(!params.marginAmount.isZero() || !params.debtAmount.isZero(), 'Deposit/debt must be non-0');
163
-
181
+
164
182
  const [vesuAdapter1] = this.getVesuAdapters();
165
183
  const lstTokenInfo = this.asset();
166
184
  const lstUnderlyingTokenInfo = vesuAdapter1.config.debt;
167
185
 
186
+ // todo make it more general
187
+ // 500k STRK (~75k$) or 0.5 BTC (~60k$)
188
+ const maxAmounts = lstTokenInfo.symbol == 'xSTRK' ? 500000 : 0.5;
189
+ if (params.marginAmount.greaterThan(maxAmounts)) {
190
+ throw new Error(`Margin amount is greater than max amount: ${params.marginAmount.toNumber()} > ${maxAmounts}`);
191
+ }
192
+
168
193
  const proofsIDs: string[] = [];
169
194
  const manageCalls: ManageCall[] = [];
170
195
 
@@ -181,13 +206,11 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<UniversalS
181
206
  }
182
207
 
183
208
  const lstDexPriceInUnderlying = params.lstDexPriceInUnderlying;
209
+ const lstTrueExchangeRate = await this.getLSTExchangeRate();
184
210
  const ekuboQuoter = new EkuboQuoter(this.config);
185
211
 
186
- // compute quotes for margin swap
187
- const marginSwap: Swap[] = [];
188
-
189
212
  // compute quotes for lever swap
190
- const MAX_SLIPPAGE = 0.01;
213
+ const MAX_SLIPPAGE = 0.002;
191
214
  // when increasing, debt is swapped to collateral (LST)
192
215
  // when decreasing, collateral is swapped to debt (underlying)
193
216
  // but both cases, we denominate amount in underlying. negative for decrease (exact amount out)
@@ -198,11 +221,29 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<UniversalS
198
221
  toToken.address.address,
199
222
  params.debtAmount // negative for exact amount out
200
223
  );
201
- assert(leverSwapQuote.price_impact < MAX_SLIPPAGE, 'getIncreaseLeverCall: Price impact is too high [Debt swap]');
224
+ logger.verbose(`${this.getTag()}::getModifyLeverCall leverSwapQuote: ${JSON.stringify(leverSwapQuote)}`);
225
+ // Ekubo's price impact can randomly show high numbers sometimes.
226
+ assert(leverSwapQuote.price_impact < 0.01, 'getIncreaseLeverCall: Price impact is too high [Debt swap]');
202
227
  const leverSwap = ekuboQuoter.getVesuMultiplyQuote(leverSwapQuote, fromToken, toToken);
203
- const minExpectedDebt = params.debtAmount.dividedBy(lstDexPriceInUnderlying).multipliedBy(1 - MAX_SLIPPAGE); // used for increase
204
- const maxUsedCollateral = params.debtAmount.abs().dividedBy(lstDexPriceInUnderlying).multipliedBy(1 + MAX_SLIPPAGE); // +ve for exact amount out, used for decrease
205
-
228
+ logger.verbose(`${this.getTag()}::getModifyLeverCall leverSwap: ${JSON.stringify(leverSwap)}`);
229
+
230
+ // todo double check this logic
231
+ // is Deposit
232
+ let minLSTReceived = params.debtAmount.dividedBy(lstDexPriceInUnderlying).multipliedBy(1 - MAX_SLIPPAGE); // used for increase
233
+ const minLSTReceivedAsPerTruePrice = params.debtAmount.dividedBy(lstTrueExchangeRate); // execution output to be <= True LST price
234
+ if (minLSTReceived < minLSTReceivedAsPerTruePrice) {
235
+ minLSTReceived = minLSTReceivedAsPerTruePrice; // the execution shouldn't be bad than True price logi
236
+ }
237
+ logger.verbose(`${this.getTag()}::getModifyLeverCall minLSTReceivedAsPerTruePrice: ${minLSTReceivedAsPerTruePrice}, minLSTReceived: ${minLSTReceived}`);
238
+
239
+ // is withdraw
240
+ let maxUsedCollateral = params.debtAmount.abs().dividedBy(lstDexPriceInUnderlying).multipliedBy(1 + MAX_SLIPPAGE); // +ve for exact amount out, used for decrease
241
+ const maxUsedCollateralInLST = params.debtAmount.abs().dividedBy(lstTrueExchangeRate).multipliedBy(1.005); // 0.5% slippage, worst case based on true price
242
+ logger.verbose(`${this.getTag()}::getModifyLeverCall maxUsedCollateralInLST: ${maxUsedCollateralInLST}, maxUsedCollateral: ${maxUsedCollateral}`);
243
+ if (maxUsedCollateralInLST > maxUsedCollateral) {
244
+ maxUsedCollateral = maxUsedCollateralInLST;
245
+ }
246
+
206
247
  const STEP2_ID = LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_ON;
207
248
  const manage2Info = this.getProofs<VesuModifyDelegationCallParams>(STEP2_ID);
208
249
  const manageCall2 = manage2Info.callConstructor({
@@ -216,10 +257,10 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<UniversalS
216
257
  isIncrease: true,
217
258
  increaseParams: {
218
259
  add_margin: params.marginAmount,
219
- margin_swap: marginSwap,
260
+ margin_swap: [],
220
261
  margin_swap_limit_amount: Web3Number.fromWei(0, this.asset().decimals),
221
262
  lever_swap: leverSwap,
222
- lever_swap_limit_amount: minExpectedDebt
263
+ lever_swap_limit_amount: minLSTReceived
223
264
  }
224
265
  } : {
225
266
  isIncrease: false,
@@ -425,7 +466,7 @@ const hyperxWBTC: UniversalStrategySettings = {
425
466
  manager: ContractAddr.from('0x75866db44c81e6986f06035206ee9c7d15833ddb22d6a22c016cfb5c866a491'),
426
467
  vaultAllocator: ContractAddr.from('0x57b5c1bb457b5e840a2714ae53ada87d77be2f3fd33a59b4fe709ef20c020c1'),
427
468
  redeemRequestNFT: ContractAddr.from('0x7a5dc288325456f05e70e9616e16bc02ffbe448f4b89f80b47c0970b989c7c'),
428
- aumOracle: ContractAddr.from(''),
469
+ aumOracle: ContractAddr.from('0x258f8a0ca0d21f542e48ad89d00e92dc4d9db4999084f50ef9c22dfb1e83023'),
429
470
  leafAdapters: [],
430
471
  adapters: [],
431
472
  targetHealthFactor: 1.1,
@@ -437,7 +478,7 @@ const hyperxtBTC: UniversalStrategySettings = {
437
478
  manager: ContractAddr.from('0xc4cc3e08029a0ae076f5fdfca70575abb78d23c5cd1c49a957f7e697885401'),
438
479
  vaultAllocator: ContractAddr.from('0x50bbd4fe69f841ecb13b2619fe50ebfa4e8944671b5d0ebf7868fd80c61b31e'),
439
480
  redeemRequestNFT: ContractAddr.from('0xeac9032f02057779816e38a6cb9185d12d86b3aacc9949b96b36de359c1e3'),
440
- aumOracle: ContractAddr.from(''),
481
+ aumOracle: ContractAddr.from('0x7e0d05cb7ba3f7db77a36c21c21583b5a524c2e685c08c24b3554911fb4a039'),
441
482
  leafAdapters: [],
442
483
  adapters: [],
443
484
  targetHealthFactor: 1.1,
@@ -449,7 +490,7 @@ const hyperxsBTC: UniversalStrategySettings = {
449
490
  manager: ContractAddr.from('0xc9ac023090625b0be3f6532ca353f086746f9c09f939dbc1b2613f09e5f821'),
450
491
  vaultAllocator: ContractAddr.from('0x60c2d856936b975459a5b4eb28b8672d91f757bd76cebb6241f8d670185dc01'),
451
492
  redeemRequestNFT: ContractAddr.from('0x429e8ee8bc7ecd1ade72630d350a2e0f10f9a2507c45f188ba17fe8f2ab4cf3'),
452
- aumOracle: ContractAddr.from(''),
493
+ aumOracle: ContractAddr.from('0x149298ade3e79ec6cbdac6cfad289c57504eaf54e590939136ed1ceca60c345'),
453
494
  leafAdapters: [],
454
495
  adapters: [],
455
496
  targetHealthFactor: 1.1,
@@ -487,7 +528,7 @@ function getStrategySettings(lstSymbol: string, underlyingSymbol: string, addres
487
528
  launchBlock: 0,
488
529
  type: 'Other',
489
530
  depositTokens: [Global.getDefaultTokens().find(token => token.symbol === lstSymbol)!],
490
- additionalInfo: getLooperSettings(lstSymbol, underlyingSymbol, addresses, VesuPools.Re7xSTRK),
531
+ additionalInfo: getLooperSettings(lstSymbol, underlyingSymbol, addresses, lstSymbol === 'xSTRK' ? VesuPools.Re7xSTRK : VesuPools.Re7xBTC),
491
532
  risk: {
492
533
  riskFactor: _riskFactor,
493
534
  netRisk:
@@ -507,8 +548,8 @@ function getStrategySettings(lstSymbol: string, underlyingSymbol: string, addres
507
548
  export const HyperLSTStrategies: IStrategyMetadata<UniversalStrategySettings>[] =
508
549
  [
509
550
  getStrategySettings('xSTRK', 'STRK', hyperxSTRK, false),
510
- getStrategySettings('xWBTC', 'WBTC', hyperxWBTC, true),
511
- getStrategySettings('xtBTC', 'tBTC', hyperxtBTC, true),
512
- getStrategySettings('xsBTC', 'solvBTC', hyperxsBTC, true),
513
- getStrategySettings('xLBTC', 'LBTC', hyperxLBTC, true),
551
+ getStrategySettings('xWBTC', 'WBTC', hyperxWBTC, false),
552
+ getStrategySettings('xtBTC', 'tBTC', hyperxtBTC, false),
553
+ getStrategySettings('xsBTC', 'solvBTC', hyperxsBTC, false),
554
+ getStrategySettings('xLBTC', 'LBTC', hyperxLBTC, false),
514
555
  ]