@strkfarm/sdk 1.1.50 → 1.1.51

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.
@@ -37,7 +37,7 @@ export class AvnuWrapper {
37
37
  excludeSources = ['Haiko(Solvers)']
38
38
  ): Promise<Quote> {
39
39
  const MAX_RETRY = 5;
40
- logger.verbose(`${AvnuWrapper.name}: getQuotes => Getting quotes for ${fromToken} -> ${toToken}, amount: ${amountWei}, taker: ${taker}, retry: ${retry}`);
40
+ // logger.verbose(`${AvnuWrapper.name}: getQuotes => Getting quotes for ${fromToken} -> ${toToken}, amount: ${amountWei}, taker: ${taker}, retry: ${retry}`);
41
41
  const params: any = {
42
42
  sellTokenAddress: fromToken,
43
43
  buyTokenAddress: toToken,
@@ -100,9 +100,9 @@ export class AvnuWrapper {
100
100
  // swapInfo as expected by the strategy
101
101
  // fallback, max 1% slippage
102
102
  const _minAmount = minAmount || (quote.buyAmount * 95n / 100n).toString();
103
- logger.verbose(`${AvnuWrapper.name}: getSwapInfo => sellToken: ${quote.sellTokenAddress}, sellAmount: ${quote.sellAmount}`);
104
- logger.verbose(`${AvnuWrapper.name}: getSwapInfo => buyToken: ${quote.buyTokenAddress}`);
105
- logger.verbose(`${AvnuWrapper.name}: getSwapInfo => buyAmount: ${quote.buyAmount}, minAmount: ${_minAmount}`);
103
+ // logger.verbose(`${AvnuWrapper.name}: getSwapInfo => sellToken: ${quote.sellTokenAddress}, sellAmount: ${quote.sellAmount}`);
104
+ // logger.verbose(`${AvnuWrapper.name}: getSwapInfo => buyToken: ${quote.buyTokenAddress}`);
105
+ // logger.verbose(`${AvnuWrapper.name}: getSwapInfo => buyAmount: ${quote.buyAmount}, minAmount: ${_minAmount}`);
106
106
  const swapInfo: SwapInfo = {
107
107
  token_from_address: quote.sellTokenAddress,
108
108
  token_from_amount: uint256.bnToUint256(quote.sellAmount),
@@ -34,23 +34,30 @@ export class Harvests {
34
34
  const unClaimed: HarvestInfo[] = [];
35
35
 
36
36
  // use the latest one
37
- const reward = rewards.sort((a, b) => b.endDate.getTime() - a.endDate.getTime())[0];
38
-
39
- const cls = await this.config.provider.getClassAt(reward.rewardsContract.address);
40
- const contract = new Contract({abi: cls.abi, address: reward.rewardsContract.address, providerOrAccount: this.config.provider});
41
- const isClaimed = await contract.call('is_claimed', [reward.claim.id]);
42
- logger.verbose(`${Harvests.name}: isClaimed: ${isClaimed}`);
43
- if (isClaimed) {
44
- return unClaimed;
45
- }
46
- // rewards contract must have enough balance to claim
47
- const bal = await (new ERC20(this.config)).balanceOf(reward.token, reward.rewardsContract.address, 18);
48
- if (bal.lessThan(reward.claim.amount)) {
49
- logger.verbose(`${Harvests.name}: balance: ${bal.toString()}, amount: ${reward.claim.amount.toString()}`);
50
- return unClaimed;
37
+ const sortedRewards = rewards.sort((a, b) => b.endDate.getTime() - a.endDate.getTime());
38
+ if (sortedRewards.length == 0) {
39
+ logger.verbose(`${Harvests.name}: no rewards found`);
40
+ return [];
51
41
  }
52
42
 
53
- unClaimed.unshift(reward); // to ensure older harvest is first
43
+ const cls = await this.config.provider.getClassAt(sortedRewards[0].rewardsContract.address);
44
+
45
+ for (const reward of sortedRewards) {
46
+ const contract = new Contract({abi: cls.abi, address: reward.rewardsContract.address, providerOrAccount: this.config.provider});
47
+ const isClaimed = await contract.call('is_claimed', [reward.claim.id]);
48
+ logger.verbose(`${Harvests.name}: isClaimed: ${isClaimed}, claim id: ${reward.claim.id}, address: ${reward.rewardsContract.address}`);
49
+ if (isClaimed) {
50
+ continue;
51
+ }
52
+ // rewards contract must have enough balance to claim
53
+ const bal = await (new ERC20(this.config)).balanceOf(reward.token, reward.rewardsContract.address, 18);
54
+ if (bal.lessThan(reward.claim.amount)) {
55
+ logger.verbose(`${Harvests.name}: balance: ${bal.toString()}, amount: ${reward.claim.amount.toString()}`);
56
+ continue;
57
+ }
58
+
59
+ unClaimed.push(reward); // to ensure older harvest is first
60
+ }
54
61
  return unClaimed;
55
62
  }
56
63
  }
@@ -4,6 +4,7 @@ import { readFileSync, existsSync, writeFileSync } from 'fs'
4
4
  import { IConfig } from '../interfaces';
5
5
  import { Store, getDefaultStoreConfig } from '../utils/store';
6
6
  import { add } from 'winston';
7
+ import { logger } from '@/utils';
7
8
 
8
9
  function getContracts() {
9
10
  const PATH = './contracts.json'
@@ -207,13 +208,47 @@ async function executeTransactions(
207
208
  return tx;
208
209
  }
209
210
 
211
+ async function myWaitForTransaction(
212
+ transaction_hash: string,
213
+ provider: RpcProvider,
214
+ retry = 0
215
+ ) {
216
+ const MAX_RETRIES = 60;
217
+ logger.verbose(`Waiting for transaction: ${transaction_hash}, retry: ${retry}`);
218
+ try {
219
+ const status = await provider.getTransactionStatus(transaction_hash);
220
+ logger.verbose(`Transaction status: ${JSON.stringify(status.execution_status)}`);
221
+ if (status.execution_status == TransactionExecutionStatus.SUCCEEDED) {
222
+ return true;
223
+ }
224
+ if (status.execution_status == TransactionExecutionStatus.REVERTED) {
225
+ throw new Error(`Transaction reverted: ${transaction_hash}`);
226
+ }
227
+ if (retry > MAX_RETRIES) {
228
+ throw new Error(`Transaction not found: ${transaction_hash}`);
229
+ }
230
+ await new Promise(resolve => setTimeout(resolve, 1000));
231
+ return myWaitForTransaction(transaction_hash, provider, retry + 1);
232
+ } catch (error) {
233
+ if (error instanceof Error && error.message.includes('Transaction reverted')) {
234
+ throw new Error(`Transaction reverted: ${transaction_hash}`);
235
+ }
236
+ if (retry > MAX_RETRIES) {
237
+ throw error;
238
+ }
239
+ await new Promise(resolve => setTimeout(resolve, 1000));
240
+ return myWaitForTransaction(transaction_hash, provider, retry + 1);
241
+ }
242
+ }
243
+
210
244
  const Deployer = {
211
245
  getAccount,
212
246
  myDeclare,
213
247
  deployContract,
214
248
  prepareMultiDeployContracts,
215
249
  executeDeployCalls,
216
- executeTransactions
250
+ executeTransactions,
251
+ myWaitForTransaction
217
252
  }
218
253
 
219
254
  export default Deployer;
@@ -1597,11 +1597,20 @@ export class EkuboCLVault extends BaseStrategy<
1597
1597
  };
1598
1598
  }
1599
1599
 
1600
- async harvest(acc: Account, maxIterations = 20, priceRatioPrecision = 4): Promise<Call[]> {
1600
+ async harvest(acc: Account, maxIterations = 20, priceRatioPrecision = 4, minRewardAmount: Web3Number = new Web3Number(0, 18)): Promise<Call[]> {
1601
1601
  const ekuboHarvests = new EkuboHarvests(this.config);
1602
- const unClaimedRewards = await ekuboHarvests.getUnHarvestedRewards(
1602
+
1603
+ // get unclaimed rewards and filter out those less than the minimum reward amount
1604
+ const unClaimedRewards = (await ekuboHarvests.getUnHarvestedRewards(
1603
1605
  this.address
1604
- );
1606
+ )).filter(claim => claim.actualReward.greaterThanOrEqualTo(minRewardAmount));
1607
+
1608
+ if (unClaimedRewards.length == 0) {
1609
+ logger.verbose(`${EkuboCLVault.name}: harvest => no unclaimed rewards found`);
1610
+ return [];
1611
+ }
1612
+
1613
+ // get necessary info for the harvest
1605
1614
  const poolKey = await this.getPoolKey();
1606
1615
  const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
1607
1616
  const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
@@ -1609,8 +1618,14 @@ export class EkuboCLVault extends BaseStrategy<
1609
1618
  logger.verbose(
1610
1619
  `${EkuboCLVault.name}: harvest => unClaimedRewards: ${unClaimedRewards.length}`
1611
1620
  );
1621
+
1622
+ // execute the harvest
1612
1623
  const calls: Call[] = [];
1613
- for (let claim of unClaimedRewards) {
1624
+ // do one at a time.
1625
+ const chosenClaim = unClaimedRewards[0];
1626
+ logger.info(`${EkuboCLVault.name}: harvest => doing one at a time`);
1627
+ logger.info(`${EkuboCLVault.name}: harvest => chosenClaim -> Claim ID: ${chosenClaim.claim.id}, Amount: ${chosenClaim.claim.amount.toString()}, actualAmount: ${chosenClaim.actualReward.toString()}, addr: ${chosenClaim.claim.claimee.toString()}`);
1628
+ for (let claim of [chosenClaim]) {
1614
1629
  const fee = claim.claim.amount
1615
1630
  .multipliedBy(this.metadata.additionalInfo.feeBps)
1616
1631
  .dividedBy(10000);
@@ -1683,60 +1698,50 @@ export class EkuboCLVault extends BaseStrategy<
1683
1698
  logger.verbose(
1684
1699
  `${
1685
1700
  EkuboCLVault.name
1686
- }: harvest => token0Amt: ${token0Amt.toString()}, token1Amt: ${token1Amt.toString()}`
1687
- );
1688
-
1689
- // THis function cannot handle swapping of non-STRK pool,
1690
- // bcz atleast one of token0Amt or token1Amt are in STRK terms.
1691
- const swapInfo = await this.getSwapInfoGivenAmounts(
1692
- poolKey,
1693
- token0Amt,
1694
- token1Amt,
1695
- bounds,
1696
- maxIterations,
1697
- priceRatioPrecision
1698
- );
1699
- swapInfo.token_to_address = token0Info.address.address;
1700
- logger.verbose(
1701
- `${EkuboCLVault.name}: harvest => swapInfo: ${JSON.stringify(swapInfo)}`
1701
+ }: harvest => token0Amt: ${token0Amt.toFixed(18)}, token1Amt: ${token1Amt.toFixed(18)}`
1702
1702
  );
1703
1703
 
1704
1704
  logger.verbose(
1705
1705
  `${EkuboCLVault.name}: harvest => claim: ${JSON.stringify(claim)}`
1706
1706
  );
1707
- const harvestEstimateCall = async (swapInfo1: SwapInfo) => {
1708
- const swap1Amount = Web3Number.fromWei(
1709
- uint256.uint256ToBN(swapInfo1.token_from_amount).toString(),
1710
- 18 // cause its always STRK?
1707
+ const claimTokenInfo = await Global.getTokenInfoFromAddr(claim.token);
1708
+ const harvestEstimateCall = async (baseSwapInfo: SwapInfo) => {
1709
+ // - the base swap if actual swap from claim token to non-claim token
1710
+ // - the other swap is just claim token to claim token (e.g. STRK to STRK)
1711
+ // which is just dummy
1712
+ let baseSwapAmount = Web3Number.fromWei(
1713
+ uint256.uint256ToBN(baseSwapInfo.token_from_amount).toString(),
1714
+ claimTokenInfo.decimals
1711
1715
  ).minimum(
1712
- postFeeAmount.toFixed(18) // cause always strk
1716
+ postFeeAmount.toFixed(claimTokenInfo.decimals)
1713
1717
  ); // ensure we don't swap more than we have
1714
- swapInfo.token_from_amount = uint256.bnToUint256(swap1Amount.toWei());
1715
- swapInfo.token_to_min_amount = uint256.bnToUint256(
1716
- swap1Amount.multipliedBy(0).toWei() // placeholder
1717
- ); // 0.01% slippage
1718
+ if (baseSwapAmount.lt(0.0001)) {
1719
+ baseSwapAmount = new Web3Number(0, claimTokenInfo.decimals);
1720
+ }
1721
+ baseSwapInfo.token_from_amount = uint256.bnToUint256(baseSwapAmount.toWei());
1718
1722
 
1723
+ const isToken0ClaimToken = claim.token.eq(poolKey.token0);
1719
1724
  logger.verbose(
1720
- `${EkuboCLVault.name}: harvest => swap1Amount: ${swap1Amount}`
1725
+ `${EkuboCLVault.name}: harvest => isToken0ClaimToken: ${isToken0ClaimToken}, baseSwapAmount: ${baseSwapAmount}`
1721
1726
  );
1722
1727
 
1723
- const remainingAmount = postFeeAmount.minus(swap1Amount).maximum(0);
1728
+ const remainingAmount = postFeeAmount.minus(baseSwapAmount).maximum(0);
1724
1729
  logger.verbose(
1725
1730
  `${EkuboCLVault.name}: harvest => remainingAmount: ${remainingAmount}`
1726
1731
  );
1727
- const swapInfo2 = {
1728
- ...swapInfo,
1729
- token_from_amount: uint256.bnToUint256(remainingAmount.toWei()),
1730
- };
1731
- swapInfo2.token_to_address = token1Info.address.address;
1732
+
1733
+ // obv, same to same
1734
+ let dummySwapInfo = AvnuWrapper.buildZeroSwap(claim.token, this.address.address, claim.token);
1735
+ dummySwapInfo.token_from_amount = uint256.bnToUint256(remainingAmount.toWei());
1736
+
1732
1737
  logger.verbose(
1733
- `${EkuboCLVault.name}: harvest => swapInfo: ${JSON.stringify(
1734
- swapInfo
1738
+ `${EkuboCLVault.name}: harvest => dummySwapInfo: ${JSON.stringify(
1739
+ dummySwapInfo
1735
1740
  )}`
1736
1741
  );
1737
1742
  logger.verbose(
1738
- `${EkuboCLVault.name}: harvest => swapInfo2: ${JSON.stringify(
1739
- swapInfo2
1743
+ `${EkuboCLVault.name}: harvest => baseSwapInfo: ${JSON.stringify(
1744
+ baseSwapInfo
1740
1745
  )}`
1741
1746
  );
1742
1747
  const calldata = [
@@ -1747,18 +1752,41 @@ export class EkuboCLVault extends BaseStrategy<
1747
1752
  claimee: claim.claim.claimee.address,
1748
1753
  },
1749
1754
  claim.proof.map((p) => num.getDecimalString(p)),
1750
- swapInfo,
1751
- swapInfo2,
1755
+ isToken0ClaimToken ? dummySwapInfo : baseSwapInfo, // is token0 claim token, its just dummy swap
1756
+ isToken0ClaimToken ? baseSwapInfo : dummySwapInfo,
1752
1757
  ];
1753
- logger.verbose(
1754
- `${EkuboCLVault.name}: harvest => calldata: ${JSON.stringify(
1755
- calldata
1756
- )}`
1757
- );
1758
1758
  return [this.contract.populate("harvest", calldata)];
1759
1759
  };
1760
+
1761
+ // if token0 == claim token, then the base swapInfo is from claim token to token1
1762
+ // if token1 == claim token, then the base swapInfo is from claim token to token0
1763
+ const isToken0ClaimToken = claim.token.eq(poolKey.token0);
1764
+ let baseSwapInfo = AvnuWrapper.buildZeroSwap(claim.token, this.address.address, isToken0ClaimToken ? token1Info.address : token0Info.address);
1765
+ baseSwapInfo.token_from_amount = uint256.bnToUint256(postFeeAmount.toWei()); // we try to swap all to start with
1766
+
1767
+ // if token0 != claim token, then we swap from claim token to token0
1768
+ if (postFeeAmount.greaterThan(0) && !isToken0ClaimToken) {
1769
+ const avnuWrapper = new AvnuWrapper();
1770
+ const quote = await avnuWrapper.getQuotes(
1771
+ claim.token.address,
1772
+ token0Info.address.address,
1773
+ postFeeAmount.toWei(),
1774
+ this.address.address
1775
+ );
1776
+ baseSwapInfo = await avnuWrapper.getSwapInfo(quote, this.address.address, 0, this.address.address);
1777
+ } else if (postFeeAmount.greaterThan(0) && isToken0ClaimToken) {
1778
+ // if token0 == claim token, then we swap from claim token to token1
1779
+ const avnuWrapper = new AvnuWrapper();
1780
+ const quote = await avnuWrapper.getQuotes(
1781
+ claim.token.address,
1782
+ token1Info.address.address,
1783
+ postFeeAmount.toWei(),
1784
+ this.address.address
1785
+ );
1786
+ baseSwapInfo = await avnuWrapper.getSwapInfo(quote, this.address.address, 0, this.address.address);
1787
+ }
1760
1788
  const _callsFinal = await this.rebalanceIter(
1761
- swapInfo,
1789
+ baseSwapInfo,
1762
1790
  acc,
1763
1791
  harvestEstimateCall,
1764
1792
  claim.token.eq(poolKey.token0),
@@ -1828,39 +1856,47 @@ export class EkuboCLVault extends BaseStrategy<
1828
1856
  }) {
1829
1857
  const { postFeeAmount, claim, token0Info, token1Info, acc } = params;
1830
1858
  let harvestCall: Call | null = null;
1859
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => postFeeAmount: ${postFeeAmount.toString()}`);
1831
1860
 
1861
+ let attempt = 0;
1862
+ let MAX_ATTEMPTS = 50;
1832
1863
  const binarySearchCallbackFn = async (mid: bigint) => {
1864
+ attempt++;
1865
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => mid: ${mid}, attempt: ${attempt}/${MAX_ATTEMPTS}`);
1833
1866
  const rewardPart2 = BigInt(postFeeAmount.toWei()) - mid;
1834
1867
  const avnuWrapper = new AvnuWrapper();
1835
1868
  const beneficiary = this.address.address;
1836
1869
 
1837
1870
  // get quote for 1st part
1838
- const quote1 = await avnuWrapper.getQuotes(
1871
+ const quote1Prom = avnuWrapper.getQuotes(
1839
1872
  claim.token.address,
1840
1873
  token0Info.address.address,
1841
1874
  mid.toString(),
1842
1875
  beneficiary
1843
1876
  );
1877
+ const quote2Prom = avnuWrapper.getQuotes(
1878
+ claim.token.address,
1879
+ token1Info.address.address,
1880
+ rewardPart2.toString(),
1881
+ beneficiary
1882
+ );
1883
+ const [quote1, quote2] = await Promise.all([quote1Prom, quote2Prom]);
1884
+
1844
1885
  // default min amount is ok
1845
1886
  const swapInfo1 = await avnuWrapper.getSwapInfo(
1846
1887
  quote1,
1847
1888
  beneficiary,
1848
- 0,
1889
+ 0, // fee bps
1849
1890
  beneficiary
1850
1891
  );
1851
1892
 
1852
1893
  // get quote for 2nd part
1853
- const quote2 = await avnuWrapper.getQuotes(
1854
- claim.token.address,
1855
- token1Info.address.address,
1856
- rewardPart2.toString(),
1857
- beneficiary
1858
- );
1894
+
1859
1895
  // default min amount is ok
1860
1896
  const swapInfo2 = await avnuWrapper.getSwapInfo(
1861
1897
  quote2,
1862
1898
  beneficiary,
1863
- 0,
1899
+ 0, // fee bps
1864
1900
  beneficiary
1865
1901
  );
1866
1902
 
@@ -1879,15 +1915,19 @@ export class EkuboCLVault extends BaseStrategy<
1879
1915
  ];
1880
1916
  harvestCall = this.contract.populate("harvest", calldata)
1881
1917
  const gas = await acc.estimateInvokeFee(harvestCall);
1918
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => gas: ${gas.overall_fee.toString()}, attempt: ${attempt}/${MAX_ATTEMPTS}`);
1882
1919
  return 'found';
1883
1920
  } catch (err: any) {
1884
1921
  if (err.message.includes('invalid token0 amount')) {
1922
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => invalid token0 amount, attempt: ${attempt}/${MAX_ATTEMPTS}`);
1885
1923
  // too much token0 amount left, may be swap less to token0
1886
1924
  return 'go_low';
1887
1925
  } else if (err.message.includes('invalid token1 amount')) {
1926
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => invalid token1 amount, attempt: ${attempt}/${MAX_ATTEMPTS}`);
1888
1927
  // too much token1 balance left, may be swap more to token0
1889
1928
  return 'go_high';
1890
1929
  }
1930
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => error: ${err.message}, attempt: ${attempt}/${MAX_ATTEMPTS}`);
1891
1931
  return 'retry';
1892
1932
  }
1893
1933
  }