@strkfarm/sdk 1.0.28 → 1.0.29

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.
@@ -15,19 +15,19 @@ var _Web3Number = class extends BigNumber {
15
15
  return this.mul(10 ** this.decimals).toFixed(0);
16
16
  }
17
17
  multipliedBy(value) {
18
- let _value = Number(value).toFixed(this.maxToFixedDecimals());
18
+ const _value = this.getStandardString(value);
19
19
  return this.construct(this.mul(_value).toString(), this.decimals);
20
20
  }
21
21
  dividedBy(value) {
22
- let _value = Number(value).toFixed(this.maxToFixedDecimals());
22
+ const _value = this.getStandardString(value);
23
23
  return this.construct(this.div(_value).toString(), this.decimals);
24
24
  }
25
25
  plus(value) {
26
- const _value = Number(value).toFixed(this.maxToFixedDecimals());
26
+ const _value = this.getStandardString(value);
27
27
  return this.construct(this.add(_value).toString(), this.decimals);
28
28
  }
29
29
  minus(n, base) {
30
- const _value = Number(n).toFixed(this.maxToFixedDecimals());
30
+ const _value = this.getStandardString(n);
31
31
  return this.construct(super.minus(_value, base).toString(), this.decimals);
32
32
  }
33
33
  construct(value, decimals) {
@@ -43,11 +43,17 @@ var _Web3Number = class extends BigNumber {
43
43
  return this.toString();
44
44
  }
45
45
  maxToFixedDecimals() {
46
- return Math.min(this.decimals, 13);
46
+ return Math.min(this.decimals, 18);
47
+ }
48
+ getStandardString(value) {
49
+ if (typeof value == "string") {
50
+ return value;
51
+ }
52
+ return value.toFixed(this.maxToFixedDecimals());
47
53
  }
48
54
  };
49
- BigNumber.config({ DECIMAL_PLACES: 18 });
50
- _Web3Number.config({ DECIMAL_PLACES: 18 });
55
+ BigNumber.config({ DECIMAL_PLACES: 18, ROUNDING_MODE: BigNumber.ROUND_DOWN });
56
+ _Web3Number.config({ DECIMAL_PLACES: 18, ROUNDING_MODE: BigNumber.ROUND_DOWN });
51
57
 
52
58
  // src/dataTypes/bignumber.browser.ts
53
59
  var Web3Number = class _Web3Number2 extends _Web3Number {
@@ -110,42 +116,48 @@ var defaultTokens = [{
110
116
  logo: "https://assets.coingecko.com/coins/images/26433/small/starknet.png",
111
117
  address: ContractAddr.from("0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d"),
112
118
  decimals: 18,
113
- coingeckId: "starknet"
119
+ coingeckId: "starknet",
120
+ displayDecimals: 2
114
121
  }, {
115
122
  name: "xSTRK",
116
123
  symbol: "xSTRK",
117
124
  logo: "https://dashboard.endur.fi/endur-fi.svg",
118
125
  address: ContractAddr.from("0x028d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a"),
119
126
  decimals: 18,
120
- coingeckId: void 0
127
+ coingeckId: void 0,
128
+ displayDecimals: 2
121
129
  }, {
122
130
  name: "ETH",
123
131
  symbol: "ETH",
124
132
  logo: "https://opbnb.bscscan.com/token/images/ether.svg",
125
133
  address: ContractAddr.from("0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"),
126
134
  decimals: 18,
127
- coingeckId: void 0
135
+ coingeckId: void 0,
136
+ displayDecimals: 4
128
137
  }, {
129
138
  name: "USDC",
130
139
  symbol: "USDC",
131
140
  logo: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
132
141
  address: ContractAddr.from("0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"),
133
142
  decimals: 6,
134
- coingeckId: void 0
143
+ coingeckId: void 0,
144
+ displayDecimals: 2
135
145
  }, {
136
146
  name: "USDT",
137
147
  symbol: "USDT",
138
148
  logo: "https://assets.coingecko.com/coins/images/325/small/Tether.png",
139
149
  address: ContractAddr.from("0x68f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"),
140
150
  decimals: 6,
141
- coingeckId: void 0
151
+ coingeckId: void 0,
152
+ displayDecimals: 2
142
153
  }, {
143
154
  name: "WBTC",
144
155
  symbol: "WBTC",
145
156
  logo: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599/logo.png",
146
157
  address: ContractAddr.from("0x3fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac"),
147
158
  decimals: 8,
148
- coingeckId: void 0
159
+ coingeckId: void 0,
160
+ displayDecimals: 6
149
161
  }];
150
162
  var tokens = defaultTokens;
151
163
  var Global = class _Global {
@@ -177,7 +189,8 @@ var Global = class _Global {
177
189
  address: ContractAddr.from(token.address),
178
190
  decimals: token.decimals,
179
191
  logo: token.logoUri,
180
- coingeckId: token.extensions.coingeckoId
192
+ coingeckId: token.extensions.coingeckoId,
193
+ displayDecimals: 2
181
194
  });
182
195
  });
183
196
  console.log(tokens);
@@ -553,7 +566,8 @@ var _ZkLend = class _ZkLend extends ILending {
553
566
  logo: "",
554
567
  decimals: pool.token.decimals,
555
568
  borrowFactor: Web3Number.fromWei(pool.borrow_factor.value, pool.borrow_factor.decimals),
556
- collareralFactor
569
+ collareralFactor,
570
+ displayDecimals: 2
557
571
  };
558
572
  this.tokens.push(token);
559
573
  });
@@ -670,7 +684,7 @@ var PricerFromApi = class extends PricerBase {
670
684
  try {
671
685
  return await this.getPriceFromMyAPI(tokenSymbol);
672
686
  } catch (e) {
673
- logger.warn("getPriceFromMyAPI error", e);
687
+ logger.warn("getPriceFromMyAPI error", JSON.stringify(e.message || e));
674
688
  }
675
689
  logger.log("getPrice coinbase", tokenSymbol);
676
690
  let retry = 0;
@@ -690,7 +704,7 @@ var PricerFromApi = class extends PricerBase {
690
704
  timestamp: /* @__PURE__ */ new Date()
691
705
  };
692
706
  } catch (e) {
693
- logger.warn("getPrice coinbase error", e, retry);
707
+ logger.warn("getPrice coinbase error", JSON.stringify(e.message || e));
694
708
  await new Promise((resolve) => setTimeout(resolve, retry * 1e3));
695
709
  }
696
710
  }
@@ -704,9 +718,6 @@ var PricerFromApi = class extends PricerBase {
704
718
  const priceInfo = await priceInfoRes.json();
705
719
  const now = /* @__PURE__ */ new Date();
706
720
  const priceTime = new Date(priceInfo.timestamp);
707
- if (now.getTime() - priceTime.getTime() > 9e5) {
708
- throw new Error("Price is stale");
709
- }
710
721
  const price = Number(priceInfo.price);
711
722
  return {
712
723
  price,
@@ -1870,17 +1881,28 @@ function assert(condition, message) {
1870
1881
  }
1871
1882
 
1872
1883
  // src/modules/avnu.ts
1873
- var AvnuWrapper = class {
1874
- async getQuotes(fromToken, toToken, amountWei, taker) {
1884
+ var AvnuWrapper = class _AvnuWrapper {
1885
+ async getQuotes(fromToken, toToken, amountWei, taker, retry = 0) {
1886
+ const MAX_RETRY = 5;
1887
+ logger.verbose(`${_AvnuWrapper.name}: getQuotes => Getting quotes for ${fromToken} -> ${toToken}, amount: ${amountWei}, taker: ${taker}, retry: ${retry}`);
1875
1888
  const params = {
1876
1889
  sellTokenAddress: fromToken,
1877
1890
  buyTokenAddress: toToken,
1878
1891
  sellAmount: amountWei,
1879
- takerAddress: taker
1892
+ takerAddress: taker,
1893
+ // excludeSources: ['Nostra', 'Haiko(Solvers)']
1894
+ excludeSources: ["Haiko(Solvers)"]
1895
+ // to resolve InvalidOraclePrice error
1880
1896
  };
1881
1897
  assert(fromToken != toToken, "From and to tokens are the same");
1882
1898
  const quotes = await fetchQuotes(params);
1883
- assert(quotes.length > 0, "No quotes found");
1899
+ if (quotes.length == 0) {
1900
+ if (retry < MAX_RETRY) {
1901
+ await new Promise((res) => setTimeout(res, 3e3));
1902
+ return await this.getQuotes(fromToken, toToken, amountWei, taker, retry + 1);
1903
+ }
1904
+ throw new Error("no quotes found");
1905
+ }
1884
1906
  return quotes[0];
1885
1907
  }
1886
1908
  async getSwapInfo(quote, taker, integratorFeeBps, integratorFeeRecipient, minAmount) {
@@ -3553,10 +3575,10 @@ var BaseStrategy = class {
3553
3575
  async getTVL() {
3554
3576
  throw new Error("Not implemented");
3555
3577
  }
3556
- depositCall(amountInfo, receiver) {
3578
+ async depositCall(amountInfo, receiver) {
3557
3579
  throw new Error("Not implemented");
3558
3580
  }
3559
- withdrawCall(amountInfo, receiver, owner) {
3581
+ async withdrawCall(amountInfo, receiver, owner) {
3560
3582
  throw new Error("Not implemented");
3561
3583
  }
3562
3584
  };
@@ -3593,7 +3615,7 @@ var VesuRebalance = class _VesuRebalance extends BaseStrategy {
3593
3615
  * @param receiver - Address that will receive the strategy tokens
3594
3616
  * @returns Populated contract call for deposit
3595
3617
  */
3596
- depositCall(amountInfo, receiver) {
3618
+ async depositCall(amountInfo, receiver) {
3597
3619
  assert(amountInfo.tokenInfo.address.eq(this.asset().address), "Deposit token mismatch");
3598
3620
  const assetContract = new Contract4(vesu_rebalance_abi_default, this.asset().address.address, this.config.provider);
3599
3621
  const call1 = assetContract.populate("approve", [this.address.address, uint2563.bnToUint256(amountInfo.amount.toWei())]);
@@ -3607,7 +3629,7 @@ var VesuRebalance = class _VesuRebalance extends BaseStrategy {
3607
3629
  * @param owner - Address that owns the strategy tokens
3608
3630
  * @returns Populated contract call for withdrawal
3609
3631
  */
3610
- withdrawCall(amountInfo, receiver, owner) {
3632
+ async withdrawCall(amountInfo, receiver, owner) {
3611
3633
  return [this.contract.populate("withdraw", [uint2563.bnToUint256(amountInfo.amount.toWei()), receiver.address, owner.address])];
3612
3634
  }
3613
3635
  /**
@@ -4010,7 +4032,7 @@ var VesuRebalanceStrategies = [{
4010
4032
  }];
4011
4033
 
4012
4034
  // src/strategies/ekubo-cl-vault.ts
4013
- import { Contract as Contract5, uint256 as uint2564 } from "starknet";
4035
+ import { Contract as Contract6, num as num3, uint256 as uint2564 } from "starknet";
4014
4036
 
4015
4037
  // src/data/cl-vault.abi.json
4016
4038
  var cl_vault_abi_default = [
@@ -8911,6 +8933,58 @@ var erc4626_abi_default = [
8911
8933
  }
8912
8934
  ];
8913
8935
 
8936
+ // src/modules/harvests.ts
8937
+ import { Contract as Contract5 } from "starknet";
8938
+ var Harvests = class _Harvests {
8939
+ constructor(config) {
8940
+ this.config = config;
8941
+ }
8942
+ getHarvests(addr) {
8943
+ throw new Error("Not implemented");
8944
+ }
8945
+ async getUnHarvestedRewards(addr) {
8946
+ const rewards = await this.getHarvests(addr);
8947
+ if (rewards.length == 0) return [];
8948
+ const unClaimed = [];
8949
+ const cls = await this.config.provider.getClassAt(rewards[0].rewardsContract.address);
8950
+ for (let reward of rewards) {
8951
+ const contract = new Contract5(cls.abi, reward.rewardsContract.address, this.config.provider);
8952
+ const isClaimed = await contract.call("is_claimed", [reward.claim.id]);
8953
+ logger.verbose(`${_Harvests.name}: isClaimed: ${isClaimed}`);
8954
+ if (isClaimed)
8955
+ return unClaimed;
8956
+ unClaimed.unshift(reward);
8957
+ }
8958
+ return unClaimed;
8959
+ }
8960
+ };
8961
+ var EkuboHarvests = class extends Harvests {
8962
+ async getHarvests(addr) {
8963
+ const STRK = "0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
8964
+ const EKUBO_API = `https://starknet-mainnet-api.ekubo.org/airdrops/${addr.address}?token=${STRK}`;
8965
+ const resultEkubo = await fetch(EKUBO_API);
8966
+ const items = await resultEkubo.json();
8967
+ const rewards = [];
8968
+ for (let i = 0; i < items.length; ++i) {
8969
+ const info = items[i];
8970
+ assert(info.token == STRK, "expected strk token only");
8971
+ rewards.push({
8972
+ rewardsContract: ContractAddr.from(info.contract_address),
8973
+ token: ContractAddr.from(STRK),
8974
+ startDate: new Date(info.start_date),
8975
+ endDate: new Date(info.end_date),
8976
+ claim: {
8977
+ id: info.claim.id,
8978
+ amount: Web3Number.fromWei(info.claim.amount, 18),
8979
+ claimee: ContractAddr.from(info.claim.claimee)
8980
+ },
8981
+ proof: info.proof
8982
+ });
8983
+ }
8984
+ return rewards.sort((a, b) => b.endDate.getTime() - a.endDate.getTime());
8985
+ }
8986
+ };
8987
+
8914
8988
  // src/strategies/ekubo-cl-vault.ts
8915
8989
  var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
8916
8990
  /**
@@ -8927,19 +9001,74 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
8927
9001
  assert(metadata.depositTokens.length === 2, "EkuboCL only supports 2 deposit token");
8928
9002
  this.metadata = metadata;
8929
9003
  this.address = metadata.address;
8930
- this.contract = new Contract5(cl_vault_abi_default, this.address.address, this.config.provider);
8931
- this.lstContract = new Contract5(erc4626_abi_default, this.metadata.additionalInfo.lstContract.address, this.config.provider);
9004
+ this.contract = new Contract6(cl_vault_abi_default, this.address.address, this.config.provider);
9005
+ this.lstContract = new Contract6(erc4626_abi_default, this.metadata.additionalInfo.lstContract.address, this.config.provider);
8932
9006
  const EKUBO_POSITION = "0x02e0af29598b407c8716b17f6d2795eca1b471413fa03fb145a5e33722184067";
8933
- this.ekuboPositionsContract = new Contract5(ekubo_positions_abi_default, EKUBO_POSITION, this.config.provider);
9007
+ this.ekuboPositionsContract = new Contract6(ekubo_positions_abi_default, EKUBO_POSITION, this.config.provider);
8934
9008
  const EKUBO_MATH = "0x04a72e9e166f6c0e9d800af4dc40f6b6fb4404b735d3f528d9250808b2481995";
8935
- this.ekuboMathContract = new Contract5(ekubo_math_abi_default, EKUBO_MATH, this.config.provider);
9009
+ this.ekuboMathContract = new Contract6(ekubo_math_abi_default, EKUBO_MATH, this.config.provider);
8936
9010
  this.avnu = new AvnuWrapper();
8937
9011
  }
8938
- depositCall(amountInfo, receiver) {
8939
- return [];
9012
+ async matchInputAmounts(amountInfo) {
9013
+ const bounds = await this.getCurrentBounds();
9014
+ const res = await this._getExpectedAmountsForLiquidity(
9015
+ amountInfo.token0.amount,
9016
+ amountInfo.token1.amount,
9017
+ bounds,
9018
+ false
9019
+ );
9020
+ return {
9021
+ token0: {
9022
+ tokenInfo: amountInfo.token0.tokenInfo,
9023
+ amount: res.amount0
9024
+ },
9025
+ token1: {
9026
+ tokenInfo: amountInfo.token1.tokenInfo,
9027
+ amount: res.amount1
9028
+ }
9029
+ };
9030
+ }
9031
+ /** Returns minimum amounts give given two amounts based on what can be added for liq */
9032
+ async getMinDepositAmounts(amountInfo) {
9033
+ const shares = await this.tokensToShares(amountInfo);
9034
+ const { amount0, amount1 } = await this.contract.call("convert_to_assets", [uint2564.bnToUint256(shares.toWei())]);
9035
+ return {
9036
+ token0: {
9037
+ tokenInfo: amountInfo.token0.tokenInfo,
9038
+ amount: Web3Number.fromWei(amount0.toString(), amountInfo.token0.tokenInfo.decimals)
9039
+ },
9040
+ token1: {
9041
+ tokenInfo: amountInfo.token1.tokenInfo,
9042
+ amount: Web3Number.fromWei(amount1.toString(), amountInfo.token1.tokenInfo.decimals)
9043
+ }
9044
+ };
8940
9045
  }
8941
- withdrawCall(amountInfo, receiver, owner) {
8942
- return [];
9046
+ async depositCall(amountInfo, receiver) {
9047
+ const updateAmountInfo = await this.getMinDepositAmounts(amountInfo);
9048
+ const token0Contract = new Contract6(erc4626_abi_default, amountInfo.token0.tokenInfo.address.address, this.config.provider);
9049
+ const token1Contract = new Contract6(erc4626_abi_default, amountInfo.token1.tokenInfo.address.address, this.config.provider);
9050
+ const call1 = token0Contract.populate("approve", [this.address.address, uint2564.bnToUint256(updateAmountInfo.token0.amount.toWei())]);
9051
+ const call2 = token1Contract.populate("approve", [this.address.address, uint2564.bnToUint256(updateAmountInfo.token1.amount.toWei())]);
9052
+ const call3 = this.contract.populate("deposit", [uint2564.bnToUint256(updateAmountInfo.token0.amount.toWei()), uint2564.bnToUint256(updateAmountInfo.token1.amount.toWei()), receiver.address]);
9053
+ const calls = [];
9054
+ if (updateAmountInfo.token0.amount.greaterThan(0)) calls.push(call1);
9055
+ if (updateAmountInfo.token1.amount.greaterThan(0)) calls.push(call2);
9056
+ return [...calls, call3];
9057
+ }
9058
+ async tokensToShares(amountInfo) {
9059
+ const shares = await this.contract.call("convert_to_shares", [
9060
+ uint2564.bnToUint256(amountInfo.token0.amount.toWei()),
9061
+ uint2564.bnToUint256(amountInfo.token1.amount.toWei())
9062
+ ]);
9063
+ return Web3Number.fromWei(shares.toString(), 18);
9064
+ }
9065
+ async withdrawCall(amountInfo, receiver, owner) {
9066
+ const shares = await this.tokensToShares(amountInfo);
9067
+ logger.verbose(`${_EkuboCLVault.name}: withdrawCall: shares=${shares.toString()}`);
9068
+ return [this.contract.populate("withdraw", [
9069
+ uint2564.bnToUint256(shares.toWei()),
9070
+ receiver.address
9071
+ ])];
8943
9072
  }
8944
9073
  rebalanceCall(newBounds, swapParams) {
8945
9074
  return [this.contract.populate("rebalance", [
@@ -8958,14 +9087,113 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
8958
9087
  handleFeesCall() {
8959
9088
  return [this.contract.populate("handle_fees", [])];
8960
9089
  }
8961
- async getUserTVL(user) {
8962
- throw new Error("Not implemented");
9090
+ /**
9091
+ * Calculates assets before and now in a given token of TVL per share to observe growth
9092
+ * @returns {Promise<number>} The weighted average APY across all pools
9093
+ */
9094
+ async netAPY(blockIdentifier = "pending", sinceBlocks = 2e4) {
9095
+ const tvlNow = await this._getTVL(blockIdentifier);
9096
+ const supplyNow = await this.totalSupply(blockIdentifier);
9097
+ const priceNow = await this.getCurrentPrice(blockIdentifier);
9098
+ let blockNow = typeof blockIdentifier == "number" ? blockIdentifier : (await this.config.provider.getBlockLatestAccepted()).block_number;
9099
+ const blockNowTime = typeof blockIdentifier == "number" ? (await this.config.provider.getBlockWithTxs(blockIdentifier)).timestamp : (/* @__PURE__ */ new Date()).getTime() / 1e3;
9100
+ const blockBefore = blockNow - sinceBlocks;
9101
+ const adjustedSupplyNow = supplyNow.minus(await this.getHarvestRewardShares(blockBefore, blockNow));
9102
+ let blockBeforeInfo = await this.config.provider.getBlockWithTxs(blockBefore);
9103
+ const tvlBefore = await this._getTVL(blockBefore);
9104
+ const supplyBefore = await this.totalSupply(blockBefore);
9105
+ const priceBefore = await this.getCurrentPrice(blockBefore);
9106
+ const tvlInToken0Now = tvlNow.amount0.multipliedBy(priceNow.price).plus(tvlNow.amount1);
9107
+ const tvlPerShareNow = tvlInToken0Now.multipliedBy(1e18).dividedBy(adjustedSupplyNow);
9108
+ const tvlInToken0Bf = tvlBefore.amount0.multipliedBy(priceBefore.price).plus(tvlBefore.amount1);
9109
+ const tvlPerShareBf = tvlInToken0Bf.multipliedBy(1e18).dividedBy(supplyBefore);
9110
+ const timeDiffSeconds = blockNowTime - blockBeforeInfo.timestamp;
9111
+ logger.verbose(`tvlInToken0Now: ${tvlInToken0Now.toString()}`);
9112
+ logger.verbose(`tvlInToken0Bf: ${tvlInToken0Bf.toString()}`);
9113
+ logger.verbose(`tvlPerShareNow: ${tvlPerShareNow.toString()}`);
9114
+ logger.verbose(`tvlPerShareBf: ${tvlPerShareBf.toString()}`);
9115
+ logger.verbose(`Price before: ${priceBefore.price.toString()}`);
9116
+ logger.verbose(`Price now: ${priceNow.price.toString()}`);
9117
+ logger.verbose(`Supply before: ${supplyBefore.toString()}`);
9118
+ logger.verbose(`Supply now: ${adjustedSupplyNow.toString()}`);
9119
+ logger.verbose(`Time diff in seconds: ${timeDiffSeconds}`);
9120
+ const apyForGivenBlocks = Number(tvlPerShareNow.minus(tvlPerShareBf).multipliedBy(1e4).dividedBy(tvlPerShareBf)) / 1e4;
9121
+ return apyForGivenBlocks * (365 * 24 * 3600) / timeDiffSeconds;
8963
9122
  }
8964
- async getTVL() {
8965
- const result = await this.contract.call("total_liquidity", []);
8966
- const bounds = await this.getCurrentBounds();
8967
- const { amount0, amount1 } = await this.getLiquidityToAmounts(Web3Number.fromWei(result.toString(), 18), bounds);
8968
- const poolKey = await this.getPoolKey();
9123
+ async getHarvestRewardShares(fromBlock, toBlock) {
9124
+ const len = Number(await this.contract.call("get_total_rewards"));
9125
+ let shares = Web3Number.fromWei(0, 18);
9126
+ for (let i = len - 1; i > 0; --i) {
9127
+ let record = await this.contract.call("get_rewards_info", [i]);
9128
+ logger.verbose(`${_EkuboCLVault.name}: getHarvestRewardShares: ${i}`);
9129
+ console.log(record);
9130
+ const block = Number(record.block_number);
9131
+ if (block < fromBlock) {
9132
+ return shares;
9133
+ } else if (block > toBlock) {
9134
+ continue;
9135
+ } else {
9136
+ shares = shares.plus(Web3Number.fromWei(record.shares.toString(), 18));
9137
+ }
9138
+ logger.verbose(`${_EkuboCLVault.name}: getHarvestRewardShares: ${i} => ${shares.toWei()}`);
9139
+ }
9140
+ return shares;
9141
+ }
9142
+ async balanceOf(user, blockIdentifier = "pending") {
9143
+ let bal = await this.contract.call("balance_of", [user.address]);
9144
+ return Web3Number.fromWei(bal.toString(), 18);
9145
+ }
9146
+ async getUserTVL(user, blockIdentifier = "pending") {
9147
+ let bal = await this.balanceOf(user, blockIdentifier);
9148
+ const assets = await this.contract.call("convert_to_assets", [uint2564.bnToUint256(bal.toWei())], {
9149
+ blockIdentifier
9150
+ });
9151
+ const poolKey = await this.getPoolKey(blockIdentifier);
9152
+ this.assertValidDepositTokens(poolKey);
9153
+ const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
9154
+ const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
9155
+ const amount0 = Web3Number.fromWei(assets.amount0.toString(), token0Info.decimals);
9156
+ const amount1 = Web3Number.fromWei(assets.amount1.toString(), token1Info.decimals);
9157
+ const P0 = await this.pricer.getPrice(token0Info.symbol);
9158
+ const P1 = await this.pricer.getPrice(token1Info.symbol);
9159
+ const token0Usd = Number(amount0.toFixed(13)) * P0.price;
9160
+ const token1Usd = Number(amount1.toFixed(13)) * P1.price;
9161
+ return {
9162
+ usdValue: token0Usd + token1Usd,
9163
+ token0: {
9164
+ tokenInfo: token0Info,
9165
+ amount: amount0,
9166
+ usdValue: token0Usd
9167
+ },
9168
+ token1: {
9169
+ tokenInfo: token1Info,
9170
+ amount: amount1,
9171
+ usdValue: token1Usd
9172
+ }
9173
+ };
9174
+ }
9175
+ async _getTVL(blockIdentifier = "pending") {
9176
+ const result = await this.contract.call("total_liquidity", [], {
9177
+ blockIdentifier
9178
+ });
9179
+ const bounds = await this.getCurrentBounds(blockIdentifier);
9180
+ const { amount0, amount1 } = await this.getLiquidityToAmounts(Web3Number.fromWei(result.toString(), 18), bounds, blockIdentifier);
9181
+ return { amount0, amount1 };
9182
+ }
9183
+ async totalSupply(blockIdentifier = "pending") {
9184
+ const res = await this.contract.call("total_supply", [], {
9185
+ blockIdentifier
9186
+ });
9187
+ return Web3Number.fromWei(res.toString(), 18);
9188
+ }
9189
+ assertValidDepositTokens(poolKey) {
9190
+ assert(poolKey.token0.eq(this.metadata.depositTokens[0].address), "Expected token0 in depositTokens[0]");
9191
+ assert(poolKey.token1.eq(this.metadata.depositTokens[1].address), "Expected token1 in depositTokens[1]");
9192
+ }
9193
+ async getTVL(blockIdentifier = "pending") {
9194
+ const { amount0, amount1 } = await this._getTVL(blockIdentifier);
9195
+ const poolKey = await this.getPoolKey(blockIdentifier);
9196
+ this.assertValidDepositTokens(poolKey);
8969
9197
  const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
8970
9198
  const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
8971
9199
  const P0 = await this.pricer.getPrice(token0Info.symbol);
@@ -8973,7 +9201,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
8973
9201
  const token0Usd = Number(amount0.toFixed(13)) * P0.price;
8974
9202
  const token1Usd = Number(amount1.toFixed(13)) * P1.price;
8975
9203
  return {
8976
- netUsdValue: token0Usd + token1Usd,
9204
+ usdValue: token0Usd + token1Usd,
8977
9205
  token0: {
8978
9206
  tokenInfo: token0Info,
8979
9207
  amount: amount0,
@@ -9013,7 +9241,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
9013
9241
  const token0Usd = Number(token0Web3.toFixed(13)) * P0.price;
9014
9242
  const token1Usd = Number(token1Web3.toFixed(13)) * P1.price;
9015
9243
  return {
9016
- netUsdValue: token0Usd + token1Usd,
9244
+ usdValue: token0Usd + token1Usd,
9017
9245
  token0: {
9018
9246
  tokenInfo: token0Info,
9019
9247
  amount: token0Web3,
@@ -9035,11 +9263,11 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
9035
9263
  const truePrice = Number(BigInt(result.toString()) * BigInt(1e9) / BigInt(1e18)) / 1e9;
9036
9264
  return truePrice;
9037
9265
  }
9038
- async getCurrentPrice() {
9039
- const poolKey = await this.getPoolKey();
9040
- return this._getCurrentPrice(poolKey);
9266
+ async getCurrentPrice(blockIdentifier = "pending") {
9267
+ const poolKey = await this.getPoolKey(blockIdentifier);
9268
+ return this._getCurrentPrice(poolKey, blockIdentifier);
9041
9269
  }
9042
- async _getCurrentPrice(poolKey) {
9270
+ async _getCurrentPrice(poolKey, blockIdentifier = "pending") {
9043
9271
  const priceInfo = await this.ekuboPositionsContract.call("get_pool_price", [
9044
9272
  {
9045
9273
  token0: poolKey.token0.address,
@@ -9048,35 +9276,44 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
9048
9276
  tick_spacing: poolKey.tick_spacing,
9049
9277
  extension: poolKey.extension
9050
9278
  }
9051
- ]);
9279
+ ], {
9280
+ blockIdentifier
9281
+ });
9052
9282
  const sqrtRatio = _EkuboCLVault.div2Power128(BigInt(priceInfo.sqrt_ratio.toString()));
9283
+ console.log(`EkuboCLVault: getCurrentPrice: blockIdentifier: ${blockIdentifier}, sqrtRatio: ${sqrtRatio}, ${priceInfo.sqrt_ratio.toString()}`);
9053
9284
  const price = sqrtRatio * sqrtRatio;
9054
9285
  const tick = _EkuboCLVault.priceToTick(price, true, Number(poolKey.tick_spacing));
9286
+ console.log(`EkuboCLVault: getCurrentPrice: blockIdentifier: ${blockIdentifier}, price: ${price}, tick: ${tick.mag}, ${tick.sign}`);
9055
9287
  return {
9056
9288
  price,
9057
- tick: tick.mag * (tick.sign == 0 ? 1 : -1)
9289
+ tick: tick.mag * (tick.sign == 0 ? 1 : -1),
9290
+ sqrtRatio: priceInfo.sqrt_ratio.toString()
9058
9291
  };
9059
9292
  }
9060
- async getCurrentBounds() {
9061
- const result = await this.contract.call("get_position_key", []);
9293
+ async getCurrentBounds(blockIdentifier = "pending") {
9294
+ const result = await this.contract.call("get_position_key", [], {
9295
+ blockIdentifier
9296
+ });
9062
9297
  return {
9063
9298
  lowerTick: _EkuboCLVault.i129ToNumber(result.bounds.lower),
9064
9299
  upperTick: _EkuboCLVault.i129ToNumber(result.bounds.upper)
9065
9300
  };
9066
9301
  }
9067
- static div2Power128(num3) {
9068
- return Number(BigInt(num3.toString()) * 1000000n / BigInt(2 ** 128)) / 1e6;
9302
+ static div2Power128(num4) {
9303
+ return Number(BigInt(num4.toString()) * 1000000n / BigInt(2 ** 128)) / 1e6;
9069
9304
  }
9070
9305
  static priceToTick(price, isRoundDown, tickSpacing) {
9071
9306
  const value = isRoundDown ? Math.floor(Math.log(price) / Math.log(1.000001)) : Math.ceil(Math.log(price) / Math.log(1.000001));
9072
9307
  const tick = Math.floor(value / tickSpacing) * tickSpacing;
9073
9308
  return this.tickToi129(tick);
9074
9309
  }
9075
- async getPoolKey() {
9310
+ async getPoolKey(blockIdentifier = "pending") {
9076
9311
  if (this.poolKey) {
9077
9312
  return this.poolKey;
9078
9313
  }
9079
- const result = await this.contract.call("get_settings", []);
9314
+ const result = await this.contract.call("get_settings", [], {
9315
+ blockIdentifier
9316
+ });
9080
9317
  const poolKey = {
9081
9318
  token0: ContractAddr.from(result.pool_key.token0.toString()),
9082
9319
  token1: ContractAddr.from(result.pool_key.token1.toString()),
@@ -9107,25 +9344,24 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
9107
9344
  * @param amount1: amount of token1
9108
9345
  * @returns {amount0, amount1}
9109
9346
  */
9110
- async _getExpectedAmountsForLiquidity(amount0, amount1, bounds) {
9347
+ async _getExpectedAmountsForLiquidity(amount0, amount1, bounds, justUseInputAmount = true) {
9111
9348
  assert(amount0.greaterThan(0) || amount1.greaterThan(0), "Amount is 0");
9112
- const poolKey = await this.getPoolKey();
9113
- const sampleLiq = 1e18;
9349
+ const sampleLiq = 1e20;
9114
9350
  const { amount0: sampleAmount0, amount1: sampleAmount1 } = await this.getLiquidityToAmounts(Web3Number.fromWei(sampleLiq.toString(), 18), bounds);
9115
9351
  logger.verbose(`${_EkuboCLVault.name}: _getExpectedAmountsForLiquidity => sampleAmount0: ${sampleAmount0.toString()}, sampleAmount1: ${sampleAmount1.toString()}`);
9116
- assert(!sampleAmount0.eq(0) && !sampleAmount1.eq(0), "Sample amount is 0");
9352
+ assert(!sampleAmount0.eq(0) || !sampleAmount1.eq(0), "Sample amount is 0");
9117
9353
  const price = await (await this.getCurrentPrice()).price;
9118
9354
  logger.verbose(`${_EkuboCLVault.name}: _getExpectedAmountsForLiquidity => price: ${price}`);
9119
9355
  if (amount1.eq(0) && amount0.greaterThan(0)) {
9120
9356
  if (sampleAmount1.eq(0)) {
9121
9357
  return {
9122
9358
  amount0,
9123
- amount1: Web3Number.fromWei("0", 18),
9359
+ amount1: Web3Number.fromWei("0", amount1.decimals),
9124
9360
  ratio: Infinity
9125
9361
  };
9126
9362
  } else if (sampleAmount0.eq(0)) {
9127
9363
  return {
9128
- amount0: Web3Number.fromWei("0", 18),
9364
+ amount0: Web3Number.fromWei("0", amount0.decimals),
9129
9365
  amount1: amount0.multipliedBy(price),
9130
9366
  ratio: 0
9131
9367
  };
@@ -9133,21 +9369,41 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
9133
9369
  } else if (amount0.eq(0) && amount1.greaterThan(0)) {
9134
9370
  if (sampleAmount0.eq(0)) {
9135
9371
  return {
9136
- amount0: Web3Number.fromWei("0", 18),
9372
+ amount0: Web3Number.fromWei("0", amount0.decimals),
9137
9373
  amount1,
9138
9374
  ratio: 0
9139
9375
  };
9140
9376
  } else if (sampleAmount1.eq(0)) {
9141
9377
  return {
9142
9378
  amount0: amount1.dividedBy(price),
9143
- amount1: Web3Number.fromWei("0", 18),
9379
+ amount1: Web3Number.fromWei("0", amount1.decimals),
9144
9380
  ratio: Infinity
9145
9381
  };
9146
9382
  }
9147
9383
  }
9148
- const ratio = sampleAmount0.multipliedBy(1e18).dividedBy(sampleAmount1.toString()).dividedBy(1e18);
9384
+ assert(sampleAmount0.decimals == sampleAmount1.decimals, "Sample amounts have different decimals");
9385
+ const ratioWeb3Number = sampleAmount0.multipliedBy(1e18).dividedBy(sampleAmount1.toString()).dividedBy(1e18);
9386
+ const ratio = Number(ratioWeb3Number.toFixed(18));
9149
9387
  logger.verbose(`${_EkuboCLVault.name}: ${this.metadata.name} => ratio: ${ratio.toString()}`);
9150
- return this._solveExpectedAmountsEq(amount0, amount1, ratio, price);
9388
+ if (justUseInputAmount)
9389
+ return this._solveExpectedAmountsEq(amount0, amount1, ratioWeb3Number, price);
9390
+ if (amount1.eq(0) && amount0.greaterThan(0)) {
9391
+ const _amount1 = amount0.dividedBy(ratioWeb3Number);
9392
+ return {
9393
+ amount0,
9394
+ amount1: _amount1,
9395
+ ratio
9396
+ };
9397
+ } else if (amount0.eq(0) && amount1.greaterThan(0)) {
9398
+ const _amount0 = amount1.multipliedBy(ratio);
9399
+ return {
9400
+ amount0: _amount0,
9401
+ amount1,
9402
+ ratio
9403
+ };
9404
+ } else {
9405
+ throw new Error("Both amounts are non-zero, cannot compute expected amounts");
9406
+ }
9151
9407
  }
9152
9408
  _solveExpectedAmountsEq(availableAmount0, availableAmount1, ratio, price) {
9153
9409
  const y = ratio.multipliedBy(availableAmount1).minus(availableAmount0).dividedBy(ratio.plus(1 / price));
@@ -9161,10 +9417,11 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
9161
9417
  async getSwapInfoToHandleUnused(considerRebalance = true) {
9162
9418
  const poolKey = await this.getPoolKey();
9163
9419
  const erc20Mod = new ERC20(this.config);
9164
- const token0Bal1 = await erc20Mod.balanceOf(poolKey.token0, this.address.address, 18);
9165
- const token1Bal1 = await erc20Mod.balanceOf(poolKey.token1, this.address.address, 18);
9166
9420
  const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
9167
9421
  const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
9422
+ const token0Bal1 = await erc20Mod.balanceOf(poolKey.token0, this.address.address, token0Info.decimals);
9423
+ const token1Bal1 = await erc20Mod.balanceOf(poolKey.token1, this.address.address, token1Info.decimals);
9424
+ logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => token0Bal1: ${token0Bal1.toString()}, token1Bal1: ${token1Bal1.toString()}`);
9168
9425
  const token0Price = await this.pricer.getPrice(token0Info.symbol);
9169
9426
  const token1Price = await this.pricer.getPrice(token1Info.symbol);
9170
9427
  const token0PriceUsd = token0Price.price * Number(token0Bal1.toFixed(13));
@@ -9175,6 +9432,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
9175
9432
  let token0Bal = token0Bal1;
9176
9433
  let token1Bal = token1Bal1;
9177
9434
  if (considerRebalance) {
9435
+ logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => considerRebalance: true`);
9178
9436
  const tvl = await this.getTVL();
9179
9437
  token0Bal = token0Bal.plus(tvl.token0.amount.toString());
9180
9438
  token1Bal = token1Bal.plus(tvl.token1.amount.toString());
@@ -9184,7 +9442,10 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
9184
9442
  logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => token0Bal: ${token0Bal.toString()}, token1Bal: ${token1Bal.toString()}`);
9185
9443
  const newBounds = await this.getNewBounds();
9186
9444
  logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => newBounds: ${newBounds.lowerTick}, ${newBounds.upperTick}`);
9187
- let expectedAmounts = await this._getExpectedAmountsForLiquidity(token0Bal, token1Bal, newBounds);
9445
+ return await this.getSwapInfoGivenAmounts(poolKey, token0Bal, token1Bal, newBounds);
9446
+ }
9447
+ async getSwapInfoGivenAmounts(poolKey, token0Bal, token1Bal, bounds) {
9448
+ let expectedAmounts = await this._getExpectedAmountsForLiquidity(token0Bal, token1Bal, bounds);
9188
9449
  logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => expectedAmounts: ${expectedAmounts.amount0.toString()}, ${expectedAmounts.amount1.toString()}`);
9189
9450
  let retry = 0;
9190
9451
  const maxRetry = 10;
@@ -9205,6 +9466,19 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
9205
9466
  logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => tokenToSell: ${tokenToSell.address}, tokenToBuy: ${tokenToBuy.address}, amountToSell: ${amountToSell.toWei()}`);
9206
9467
  logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => remainingSellAmount: ${remainingSellAmount.toString()}`);
9207
9468
  logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => expectedRatio: ${expectedRatio}`);
9469
+ if (amountToSell.eq(0)) {
9470
+ return {
9471
+ token_from_address: tokenToSell.address,
9472
+ token_from_amount: uint2564.bnToUint256(0),
9473
+ token_to_address: tokenToSell.address,
9474
+ token_to_amount: uint2564.bnToUint256(0),
9475
+ token_to_min_amount: uint2564.bnToUint256(0),
9476
+ beneficiary: this.address.address,
9477
+ integrator_fee_amount_bps: 0,
9478
+ integrator_fee_recipient: this.address.address,
9479
+ routes: []
9480
+ };
9481
+ }
9208
9482
  const quote = await this.avnu.getQuotes(tokenToSell.address, tokenToBuy.address, amountToSell.toWei(), this.address.address);
9209
9483
  if (remainingSellAmount.eq(0)) {
9210
9484
  const minAmountOut = Web3Number.fromWei(quote.buyAmount.toString(), tokenToBuyInfo.decimals).multipliedBy(0.9999);
@@ -9227,6 +9501,73 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
9227
9501
  }
9228
9502
  throw new Error("Failed to get swap info");
9229
9503
  }
9504
+ /**
9505
+ * Attempts to rebalance the vault by iteratively adjusting swap amounts if initial attempt fails.
9506
+ * Uses binary search approach to find optimal swap amount.
9507
+ *
9508
+ * @param newBounds - The new tick bounds to rebalance to
9509
+ * @param swapInfo - Initial swap parameters for rebalancing
9510
+ * @param acc - Account to estimate gas fees with
9511
+ * @param retry - Current retry attempt number (default 0)
9512
+ * @param adjustmentFactor - Percentage to adjust swap amount by (default 1)
9513
+ * @param isToken0Deficit - Whether token0 balance needs increasing (default true)
9514
+ * @returns Array of contract calls needed for rebalancing
9515
+ * @throws Error if max retries reached without successful rebalance
9516
+ */
9517
+ async rebalanceIter(swapInfo, acc, estimateCall, retry = 0, adjustmentFactor = 1, isToken0Deficit = true) {
9518
+ const MAX_RETRIES = 20;
9519
+ const MIN_ADJUSTMENT = 1e-3;
9520
+ logger.verbose(
9521
+ `Rebalancing ${this.metadata.name}: retry=${retry}, adjustment=${adjustmentFactor}%, token0Deficit=${isToken0Deficit}`
9522
+ );
9523
+ const fromAmount = uint2564.uint256ToBN(swapInfo.token_from_amount);
9524
+ logger.verbose(
9525
+ `Selling ${fromAmount.toString()} of token ${swapInfo.token_from_address}`
9526
+ );
9527
+ try {
9528
+ const calls = await estimateCall(swapInfo);
9529
+ await acc.estimateInvokeFee(calls);
9530
+ return calls;
9531
+ } catch (err) {
9532
+ if (retry >= MAX_RETRIES) {
9533
+ logger.error(`Rebalance failed after ${MAX_RETRIES} retries`);
9534
+ throw err;
9535
+ }
9536
+ if (adjustmentFactor < MIN_ADJUSTMENT) {
9537
+ logger.error("Adjustment factor too small, likely oscillating");
9538
+ throw new Error("Failed to converge on valid swap amount");
9539
+ }
9540
+ logger.error(`Rebalance attempt ${retry + 1} failed, adjusting swap amount...`);
9541
+ const newSwapInfo = { ...swapInfo };
9542
+ const currentAmount = Web3Number.fromWei(fromAmount.toString(), 18);
9543
+ if (err.message.includes("invalid token0 balance") || err.message.includes("invalid token0 amount")) {
9544
+ logger.verbose("Reducing swap amount - excess token0");
9545
+ newSwapInfo.token_from_amount = uint2564.bnToUint256(
9546
+ currentAmount.multipliedBy((100 - adjustmentFactor) / 100).toWei()
9547
+ );
9548
+ adjustmentFactor = isToken0Deficit ? adjustmentFactor * 2 : adjustmentFactor / 2;
9549
+ isToken0Deficit = true;
9550
+ } else if (err.message.includes("invalid token1 balance") || err.message.includes("invalid token1 amount")) {
9551
+ logger.verbose("Increasing swap amount - excess token1");
9552
+ newSwapInfo.token_from_amount = uint2564.bnToUint256(
9553
+ currentAmount.multipliedBy((100 + adjustmentFactor) / 100).toWei()
9554
+ );
9555
+ adjustmentFactor = isToken0Deficit ? adjustmentFactor / 2 : adjustmentFactor * 2;
9556
+ isToken0Deficit = false;
9557
+ } else {
9558
+ logger.error("Unexpected error:", err);
9559
+ }
9560
+ newSwapInfo.token_to_min_amount = uint2564.bnToUint256("0");
9561
+ return this.rebalanceIter(
9562
+ newSwapInfo,
9563
+ acc,
9564
+ estimateCall,
9565
+ retry + 1,
9566
+ adjustmentFactor,
9567
+ isToken0Deficit
9568
+ );
9569
+ }
9570
+ }
9230
9571
  static tickToi129(tick) {
9231
9572
  if (tick < 0) {
9232
9573
  return {
@@ -9249,21 +9590,23 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
9249
9590
  static tickToPrice(tick) {
9250
9591
  return Math.pow(1.000001, Number(tick));
9251
9592
  }
9252
- async getLiquidityToAmounts(liquidity, bounds) {
9253
- const currentPrice = await this.getCurrentPrice();
9254
- const lowerPrice = await _EkuboCLVault.tickToPrice(bounds.lowerTick);
9255
- const upperPrice = await _EkuboCLVault.tickToPrice(bounds.upperTick);
9593
+ async getLiquidityToAmounts(liquidity, bounds, blockIdentifier = "pending", _poolKey = null, _currentPrice = null) {
9594
+ const currentPrice = _currentPrice || await this.getCurrentPrice(blockIdentifier);
9595
+ const lowerPrice = _EkuboCLVault.tickToPrice(bounds.lowerTick);
9596
+ const upperPrice = _EkuboCLVault.tickToPrice(bounds.upperTick);
9256
9597
  logger.verbose(`${_EkuboCLVault.name}: getLiquidityToAmounts => currentPrice: ${currentPrice.price}, lowerPrice: ${lowerPrice}, upperPrice: ${upperPrice}`);
9257
9598
  const result = await this.ekuboMathContract.call("liquidity_delta_to_amount_delta", [
9258
- uint2564.bnToUint256(_EkuboCLVault.priceToSqrtRatio(currentPrice.price).toString()),
9599
+ uint2564.bnToUint256(currentPrice.sqrtRatio),
9259
9600
  {
9260
9601
  mag: liquidity.toWei(),
9261
9602
  sign: 0
9262
9603
  },
9263
9604
  uint2564.bnToUint256(_EkuboCLVault.priceToSqrtRatio(lowerPrice).toString()),
9264
9605
  uint2564.bnToUint256(_EkuboCLVault.priceToSqrtRatio(upperPrice).toString())
9265
- ]);
9266
- const poolKey = await this.getPoolKey();
9606
+ ], {
9607
+ blockIdentifier
9608
+ });
9609
+ const poolKey = _poolKey || await this.getPoolKey(blockIdentifier);
9267
9610
  const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
9268
9611
  const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
9269
9612
  const amount0 = Web3Number.fromWei(_EkuboCLVault.i129ToNumber(result.amount0).toString(), token0Info.decimals);
@@ -9273,37 +9616,116 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
9273
9616
  amount1
9274
9617
  };
9275
9618
  }
9619
+ async harvest(acc) {
9620
+ const ekuboHarvests = new EkuboHarvests(this.config);
9621
+ const unClaimedRewards = await ekuboHarvests.getUnHarvestedRewards(this.address);
9622
+ const poolKey = await this.getPoolKey();
9623
+ const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
9624
+ const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
9625
+ const bounds = await this.getCurrentBounds();
9626
+ const calls = [];
9627
+ for (let claim of unClaimedRewards) {
9628
+ const fee = claim.claim.amount.multipliedBy(this.metadata.additionalInfo.feeBps).dividedBy(1e4);
9629
+ const postFeeAmount = claim.claim.amount.minus(fee);
9630
+ const isToken1 = claim.token.eq(poolKey.token1);
9631
+ logger.verbose(`${_EkuboCLVault.name}: harvest => Processing claim, isToken1: ${isToken1} amount: ${postFeeAmount.toWei()}`);
9632
+ const token0Amt = isToken1 ? new Web3Number(0, token0Info.decimals) : postFeeAmount;
9633
+ const token1Amt = isToken1 ? postFeeAmount : new Web3Number(0, token0Info.decimals);
9634
+ logger.verbose(`${_EkuboCLVault.name}: harvest => token0Amt: ${token0Amt.toString()}, token1Amt: ${token1Amt.toString()}`);
9635
+ const swapInfo = await this.getSwapInfoGivenAmounts(poolKey, token0Amt, token1Amt, bounds);
9636
+ swapInfo.token_to_address = token0Info.address.address;
9637
+ logger.verbose(`${_EkuboCLVault.name}: harvest => swapInfo: ${JSON.stringify(swapInfo)}`);
9638
+ logger.verbose(`${_EkuboCLVault.name}: harvest => claim: ${JSON.stringify(claim)}`);
9639
+ const harvestEstimateCall = async (swapInfo1) => {
9640
+ const swap1Amount = Web3Number.fromWei(uint2564.uint256ToBN(swapInfo1.token_from_amount).toString(), 18);
9641
+ const remainingAmount = postFeeAmount.minus(swap1Amount);
9642
+ const swapInfo2 = { ...swapInfo, token_from_amount: uint2564.bnToUint256(remainingAmount.toWei()) };
9643
+ swapInfo2.token_to_address = token1Info.address.address;
9644
+ const calldata = [
9645
+ claim.rewardsContract.address,
9646
+ {
9647
+ id: claim.claim.id,
9648
+ amount: claim.claim.amount.toWei(),
9649
+ claimee: claim.claim.claimee.address
9650
+ },
9651
+ claim.proof.map((p) => num3.getDecimalString(p)),
9652
+ swapInfo,
9653
+ swapInfo2
9654
+ ];
9655
+ logger.verbose(`${_EkuboCLVault.name}: harvest => calldata: ${JSON.stringify(calldata)}`);
9656
+ return [this.contract.populate("harvest", calldata)];
9657
+ };
9658
+ const _callsFinal = await this.rebalanceIter(swapInfo, acc, harvestEstimateCall);
9659
+ logger.verbose(`${_EkuboCLVault.name}: harvest => _callsFinal: ${JSON.stringify(_callsFinal)}`);
9660
+ calls.push(..._callsFinal);
9661
+ }
9662
+ return calls;
9663
+ }
9664
+ async getInvestmentFlows() {
9665
+ const netYield = await this.netAPY();
9666
+ const poolKey = await this.getPoolKey();
9667
+ const linkedFlow = {
9668
+ title: this.metadata.name,
9669
+ subItems: [{ key: "Pool", value: `${(_EkuboCLVault.div2Power128(BigInt(poolKey.fee)) * 100).toFixed(2)}%, ${poolKey.tick_spacing} tick spacing` }],
9670
+ linkedFlows: [],
9671
+ style: { backgroundColor: "#35484f" /* Blue */.valueOf() }
9672
+ };
9673
+ const baseFlow = {
9674
+ id: "base",
9675
+ title: "Your Deposit",
9676
+ subItems: [{ key: `Net yield`, value: `${(netYield * 100).toFixed(2)}%` }, { key: `Performance Fee`, value: `${(this.metadata.additionalInfo.feeBps / 100).toFixed(2)}%` }],
9677
+ linkedFlows: [linkedFlow],
9678
+ style: { backgroundColor: "#6e53dc" /* Purple */.valueOf() }
9679
+ };
9680
+ const rebalanceFlow = {
9681
+ id: "rebalance",
9682
+ title: "Automated Rebalance",
9683
+ subItems: [{
9684
+ key: "Range selection",
9685
+ value: `${this.metadata.additionalInfo.newBounds.lower * Number(poolKey.tick_spacing)} to ${this.metadata.additionalInfo.newBounds.upper * Number(poolKey.tick_spacing)} ticks`
9686
+ }],
9687
+ linkedFlows: [linkedFlow],
9688
+ style: { backgroundColor: "purple" /* Green */.valueOf() }
9689
+ };
9690
+ return [baseFlow, rebalanceFlow];
9691
+ }
9276
9692
  };
9277
- var _description2 = "Automatically rebalances liquidity near current price to maximize yield while reducing the necessity to manually rebalance positions frequently. Fees earn and Defi spring rewards are automatically re-invested.";
9693
+ var _description2 = "Deploys your {{POOL_NAME}} into an Ekubo liquidity pool, automatically rebalancing positions around the current price to optimize yield and reduce the need for manual adjustments. Trading fees and DeFi Spring rewards are automatically compounded back into the strategy. In return, you receive an ERC-20 token representing your share of the strategy. The APY is calculated based on 7-day historical performance.";
9278
9694
  var _protocol2 = { name: "Ekubo", logo: "https://app.ekubo.org/favicon.ico" };
9279
9695
  var _riskFactor2 = [
9280
9696
  { type: "Smart Contract Risk" /* SMART_CONTRACT_RISK */, value: 0.5, weight: 25 },
9281
9697
  { type: "Impermanent Loss Risk" /* IMPERMANENT_LOSS */, value: 1, weight: 75 }
9282
9698
  ];
9699
+ var AUDIT_URL2 = "https://assets.strkfarm.com/strkfarm/audit_report_vesu_and_ekubo_strats.pdf";
9283
9700
  var EkuboCLVaultStrategies = [{
9284
9701
  name: "Ekubo xSTRK/STRK",
9285
- description: _description2,
9702
+ description: _description2.replace("{{POOL_NAME}}", "xSTRK/STRK"),
9286
9703
  address: ContractAddr.from("0x01f083b98674bc21effee29ef443a00c7b9a500fd92cf30341a3da12c73f2324"),
9287
9704
  type: "Other",
9288
- depositTokens: [Global.getDefaultTokens().find((t) => t.symbol === "STRK"), Global.getDefaultTokens().find((t) => t.symbol === "xSTRK")],
9705
+ // must be same order as poolKey token0 and token1
9706
+ depositTokens: [Global.getDefaultTokens().find((t) => t.symbol === "xSTRK"), Global.getDefaultTokens().find((t) => t.symbol === "STRK")],
9289
9707
  protocols: [_protocol2],
9708
+ auditUrl: AUDIT_URL2,
9290
9709
  maxTVL: Web3Number.fromWei("0", 18),
9291
9710
  risk: {
9292
9711
  riskFactor: _riskFactor2,
9293
9712
  netRisk: _riskFactor2.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / _riskFactor2.reduce((acc, curr) => acc + curr.weight, 0),
9294
9713
  notARisks: getNoRiskTags(_riskFactor2)
9295
9714
  },
9715
+ apyMethodology: "APY based on 7-day historical performance, including fees and rewards.",
9296
9716
  additionalInfo: {
9297
9717
  newBounds: {
9298
9718
  lower: -1,
9299
9719
  upper: 1
9300
9720
  },
9301
- lstContract: ContractAddr.from("0x028d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a")
9721
+ lstContract: ContractAddr.from("0x028d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a"),
9722
+ feeBps: 1e3
9302
9723
  }
9303
9724
  }];
9304
9725
  export {
9305
9726
  AutoCompounderSTRK,
9306
9727
  AvnuWrapper,
9728
+ BaseStrategy,
9307
9729
  ContractAddr,
9308
9730
  ERC20,
9309
9731
  EkuboCLVault,