@strkfarm/sdk 1.0.51 → 1.0.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.global.js +94 -35
- package/dist/index.browser.mjs +94 -35
- package/dist/index.d.ts +24 -4
- package/dist/index.js +94 -35
- package/dist/index.mjs +94 -35
- package/package.json +4 -3
- package/src/modules/avnu.ts +7 -2
- package/src/strategies/ekubo-cl-vault.tsx +136 -71
package/dist/index.js
CHANGED
|
@@ -2022,14 +2022,16 @@ var AvnuWrapper = class _AvnuWrapper {
|
|
|
2022
2022
|
};
|
|
2023
2023
|
assert(fromToken != toToken, "From and to tokens are the same");
|
|
2024
2024
|
const quotes = await (0, import_avnu_sdk.fetchQuotes)(params);
|
|
2025
|
-
|
|
2025
|
+
const filteredQuotes = quotes.filter((q) => q.sellAmount.toString() == amountWei);
|
|
2026
|
+
if (filteredQuotes.length == 0) {
|
|
2026
2027
|
if (retry < MAX_RETRY) {
|
|
2027
2028
|
await new Promise((res) => setTimeout(res, 3e3));
|
|
2028
2029
|
return await this.getQuotes(fromToken, toToken, amountWei, taker, retry + 1);
|
|
2029
2030
|
}
|
|
2030
2031
|
throw new Error("no quotes found");
|
|
2031
2032
|
}
|
|
2032
|
-
|
|
2033
|
+
logger.verbose(`${_AvnuWrapper.name}: getQuotes => Found ${JSON.stringify(filteredQuotes[0])}`);
|
|
2034
|
+
return filteredQuotes[0];
|
|
2033
2035
|
}
|
|
2034
2036
|
async getSwapInfo(quote, taker, integratorFeeBps, integratorFeeRecipient, minAmount) {
|
|
2035
2037
|
const calldata = await (0, import_avnu_sdk.fetchBuildExecuteTransaction)(quote.quoteId);
|
|
@@ -2052,6 +2054,7 @@ var AvnuWrapper = class _AvnuWrapper {
|
|
|
2052
2054
|
startIndex += 5 + swap_params_len;
|
|
2053
2055
|
}
|
|
2054
2056
|
const _minAmount = minAmount || (quote.buyAmount * 95n / 100n).toString();
|
|
2057
|
+
logger.verbose(`${_AvnuWrapper.name}: getSwapInfo => sellToken: ${quote.sellTokenAddress}, sellAmount: ${quote.sellAmount}`);
|
|
2055
2058
|
logger.verbose(`${_AvnuWrapper.name}: getSwapInfo => buyToken: ${quote.buyTokenAddress}`);
|
|
2056
2059
|
logger.verbose(`${_AvnuWrapper.name}: getSwapInfo => buyAmount: ${quote.buyAmount}, minAmount: ${_minAmount}`);
|
|
2057
2060
|
const swapInfo = {
|
|
@@ -15553,7 +15556,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15553
15556
|
};
|
|
15554
15557
|
}
|
|
15555
15558
|
}
|
|
15556
|
-
const ratioWeb3Number =
|
|
15559
|
+
const ratioWeb3Number = this.getRatio(sampleAmount0, sampleAmount1);
|
|
15557
15560
|
const ratio = Number(ratioWeb3Number.toFixed(18));
|
|
15558
15561
|
logger.verbose(
|
|
15559
15562
|
`${_EkuboCLVault.name}: ${this.metadata.name} => ratio: ${ratio.toString()}`
|
|
@@ -15585,11 +15588,18 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15585
15588
|
);
|
|
15586
15589
|
}
|
|
15587
15590
|
}
|
|
15591
|
+
getRatio(token0Amt, token1Amount) {
|
|
15592
|
+
const ratio = token0Amt.multipliedBy(1e18).dividedBy(token1Amount.toString()).dividedBy(1e18);
|
|
15593
|
+
logger.verbose(
|
|
15594
|
+
`${_EkuboCLVault.name}: getRatio => token0Amt: ${token0Amt.toString()}, token1Amount: ${token1Amount.toString()}, ratio: ${ratio.toString()}`
|
|
15595
|
+
);
|
|
15596
|
+
return ratio;
|
|
15597
|
+
}
|
|
15588
15598
|
_solveExpectedAmountsEq(availableAmount0, availableAmount1, ratio, price) {
|
|
15589
15599
|
const y = ratio.multipliedBy(availableAmount1).minus(availableAmount0).dividedBy(ratio.plus(1 / price));
|
|
15590
15600
|
const x = y.dividedBy(price);
|
|
15591
15601
|
logger.verbose(
|
|
15592
|
-
`${_EkuboCLVault.name}: _solveExpectedAmountsEq => x: ${x.toString()}, y: ${y.toString()}, amount0: ${availableAmount0.toString()}, amount1: ${availableAmount1.toString()}`
|
|
15602
|
+
`${_EkuboCLVault.name}: _solveExpectedAmountsEq => ratio: ${ratio.toString()}, x: ${x.toString()}, y: ${y.toString()}, amount0: ${availableAmount0.toString()}, amount1: ${availableAmount1.toString()}`
|
|
15593
15603
|
);
|
|
15594
15604
|
if (ratio.eq(0)) {
|
|
15595
15605
|
return {
|
|
@@ -15639,7 +15649,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15639
15649
|
}
|
|
15640
15650
|
};
|
|
15641
15651
|
}
|
|
15642
|
-
async getSwapInfoToHandleUnused(considerRebalance = true, newBounds = null) {
|
|
15652
|
+
async getSwapInfoToHandleUnused(considerRebalance = true, newBounds = null, maxIterations = 20, priceRatioPrecision = 4) {
|
|
15643
15653
|
const poolKey = await this.getPoolKey();
|
|
15644
15654
|
const unusedBalances = await this.unusedBalances(poolKey);
|
|
15645
15655
|
const { amount: token0Bal1, usdValue: token0PriceUsd } = unusedBalances.token0;
|
|
@@ -15672,14 +15682,59 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15672
15682
|
logger.verbose(
|
|
15673
15683
|
`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => newBounds: ${ekuboBounds.lowerTick}, ${ekuboBounds.upperTick}`
|
|
15674
15684
|
);
|
|
15685
|
+
this.assertValidBounds(ekuboBounds);
|
|
15675
15686
|
return await this.getSwapInfoGivenAmounts(
|
|
15676
15687
|
poolKey,
|
|
15677
15688
|
token0Bal,
|
|
15678
15689
|
token1Bal,
|
|
15679
|
-
ekuboBounds
|
|
15690
|
+
ekuboBounds,
|
|
15691
|
+
maxIterations,
|
|
15692
|
+
priceRatioPrecision
|
|
15693
|
+
);
|
|
15694
|
+
}
|
|
15695
|
+
assertValidBounds(bounds) {
|
|
15696
|
+
assert(
|
|
15697
|
+
bounds.lowerTick < bounds.upperTick,
|
|
15698
|
+
`Invalid bounds: lowerTick (${bounds.lowerTick}) must be less than upperTick (${bounds.upperTick})`
|
|
15680
15699
|
);
|
|
15700
|
+
assert(Number(bounds.lowerTick) % Number(this.poolKey?.tick_spacing) === 0, `Lower tick (${bounds.lowerTick}) must be a multiple of tick spacing (${this.poolKey?.tick_spacing})`);
|
|
15701
|
+
assert(Number(bounds.upperTick) % Number(this.poolKey?.tick_spacing) === 0, `Upper tick (${bounds.upperTick}) must be a multiple of tick spacing (${this.poolKey?.tick_spacing})`);
|
|
15681
15702
|
}
|
|
15682
|
-
|
|
15703
|
+
// Helper to check for invalid states:
|
|
15704
|
+
// Throws if both tokens are decreased or both are increased, which is not expected
|
|
15705
|
+
assertValidAmounts(expectedAmounts, token0Bal, token1Bal) {
|
|
15706
|
+
if (expectedAmounts.amount0.lessThan(token0Bal) && expectedAmounts.amount1.lessThan(token1Bal)) {
|
|
15707
|
+
throw new Error("Both tokens are decreased, something is wrong");
|
|
15708
|
+
}
|
|
15709
|
+
if (expectedAmounts.amount0.greaterThan(token0Bal) && expectedAmounts.amount1.greaterThan(token1Bal)) {
|
|
15710
|
+
throw new Error("Both tokens are increased, something is wrong");
|
|
15711
|
+
}
|
|
15712
|
+
}
|
|
15713
|
+
// Helper to determine which token to sell, which to buy, and the amounts to use
|
|
15714
|
+
getSwapParams(expectedAmounts, poolKey, token0Bal, token1Bal) {
|
|
15715
|
+
const tokenToSell = expectedAmounts.amount0.lessThan(token0Bal) ? poolKey.token0 : poolKey.token1;
|
|
15716
|
+
const tokenToBuy = tokenToSell == poolKey.token0 ? poolKey.token1 : poolKey.token0;
|
|
15717
|
+
const amountToSell = tokenToSell == poolKey.token0 ? token0Bal.minus(expectedAmounts.amount0) : token1Bal.minus(expectedAmounts.amount1);
|
|
15718
|
+
if (amountToSell.eq(0)) {
|
|
15719
|
+
throw new Error(
|
|
15720
|
+
`No amount to sell for ${tokenToSell.address}`
|
|
15721
|
+
);
|
|
15722
|
+
}
|
|
15723
|
+
const remainingSellAmount = tokenToSell == poolKey.token0 ? expectedAmounts.amount0 : expectedAmounts.amount1;
|
|
15724
|
+
return { tokenToSell, tokenToBuy, amountToSell, remainingSellAmount };
|
|
15725
|
+
}
|
|
15726
|
+
/**
|
|
15727
|
+
* @description Calculates swap info based on given amounts of token0 and token1
|
|
15728
|
+
* Use token0 and token1 balances to determine the expected amounts for new bounds
|
|
15729
|
+
* @param poolKey
|
|
15730
|
+
* @param token0Bal
|
|
15731
|
+
* @param token1Bal
|
|
15732
|
+
* @param bounds // new bounds
|
|
15733
|
+
* @param maxIterations
|
|
15734
|
+
* @returns {Promise<SwapInfo>}
|
|
15735
|
+
*
|
|
15736
|
+
*/
|
|
15737
|
+
async getSwapInfoGivenAmounts(poolKey, token0Bal, token1Bal, bounds, maxIterations = 20, priceRatioPrecision = 4) {
|
|
15683
15738
|
logger.verbose(
|
|
15684
15739
|
`${_EkuboCLVault.name}: getSwapInfoGivenAmounts::pre => token0Bal: ${token0Bal.toString()}, token1Bal: ${token1Bal.toString()}`
|
|
15685
15740
|
);
|
|
@@ -15692,29 +15747,14 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15692
15747
|
`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => expectedAmounts2: ${expectedAmounts.amount0.toString()}, ${expectedAmounts.amount1.toString()}`
|
|
15693
15748
|
);
|
|
15694
15749
|
let retry = 0;
|
|
15695
|
-
const maxRetry =
|
|
15696
|
-
function assertValidAmounts(expectedAmounts2, token0Bal2, token1Bal2) {
|
|
15697
|
-
if (expectedAmounts2.amount0.lessThan(token0Bal2) && expectedAmounts2.amount1.lessThan(token1Bal2)) {
|
|
15698
|
-
throw new Error("Both tokens are decreased, something is wrong");
|
|
15699
|
-
}
|
|
15700
|
-
if (expectedAmounts2.amount0.greaterThan(token0Bal2) && expectedAmounts2.amount1.greaterThan(token1Bal2)) {
|
|
15701
|
-
throw new Error("Both tokens are increased, something is wrong");
|
|
15702
|
-
}
|
|
15703
|
-
}
|
|
15704
|
-
function getSwapParams(expectedAmounts2, poolKey2, token0Bal2, token1Bal2) {
|
|
15705
|
-
const tokenToSell = expectedAmounts2.amount0.lessThan(token0Bal2) ? poolKey2.token0 : poolKey2.token1;
|
|
15706
|
-
const tokenToBuy = tokenToSell == poolKey2.token0 ? poolKey2.token1 : poolKey2.token0;
|
|
15707
|
-
const amountToSell = tokenToSell == poolKey2.token0 ? token0Bal2.minus(expectedAmounts2.amount0) : token1Bal2.minus(expectedAmounts2.amount1);
|
|
15708
|
-
const remainingSellAmount = tokenToSell == poolKey2.token0 ? expectedAmounts2.amount0 : expectedAmounts2.amount1;
|
|
15709
|
-
return { tokenToSell, tokenToBuy, amountToSell, remainingSellAmount };
|
|
15710
|
-
}
|
|
15750
|
+
const maxRetry = maxIterations;
|
|
15711
15751
|
while (retry < maxRetry) {
|
|
15712
15752
|
retry++;
|
|
15713
15753
|
logger.verbose(
|
|
15714
15754
|
`getSwapInfoGivenAmounts::Retry attempt: ${retry}/${maxRetry}`
|
|
15715
15755
|
);
|
|
15716
|
-
assertValidAmounts(expectedAmounts, token0Bal, token1Bal);
|
|
15717
|
-
const { tokenToSell, tokenToBuy, amountToSell, remainingSellAmount } = getSwapParams(expectedAmounts, poolKey, token0Bal, token1Bal);
|
|
15756
|
+
this.assertValidAmounts(expectedAmounts, token0Bal, token1Bal);
|
|
15757
|
+
const { tokenToSell, tokenToBuy, amountToSell, remainingSellAmount } = this.getSwapParams(expectedAmounts, poolKey, token0Bal, token1Bal);
|
|
15718
15758
|
const tokenToBuyInfo = await Global.getTokenInfoFromAddr(tokenToBuy);
|
|
15719
15759
|
const expectedRatio = expectedAmounts.ratio;
|
|
15720
15760
|
logger.verbose(
|
|
@@ -15752,6 +15792,9 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15752
15792
|
quote.buyAmount.toString(),
|
|
15753
15793
|
tokenToBuyInfo.decimals
|
|
15754
15794
|
);
|
|
15795
|
+
logger.verbose(
|
|
15796
|
+
`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => amountOut: ${amountOut.toString()}`
|
|
15797
|
+
);
|
|
15755
15798
|
const swapPrice = tokenToSell == poolKey.token0 ? amountOut.dividedBy(amountToSell) : amountToSell.dividedBy(amountOut);
|
|
15756
15799
|
const newRatio = tokenToSell == poolKey.token0 ? remainingSellAmount.dividedBy(token1Bal.plus(amountOut)) : token0Bal.plus(amountOut).dividedBy(remainingSellAmount);
|
|
15757
15800
|
logger.verbose(
|
|
@@ -15761,31 +15804,46 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15761
15804
|
newRatio: newRatio.toString()
|
|
15762
15805
|
})}`
|
|
15763
15806
|
);
|
|
15764
|
-
const expectedPrecision = Math.min(
|
|
15765
|
-
|
|
15766
|
-
|
|
15807
|
+
const expectedPrecision = Math.min(priceRatioPrecision);
|
|
15808
|
+
const isWithInTolerance = Number(newRatio.toString()) <= expectedRatio * (1 + 1 / 10 ** expectedPrecision) && Number(newRatio.toString()) >= expectedRatio * (1 - 1 / 10 ** expectedPrecision);
|
|
15809
|
+
const currentPrecision = (expectedRatio - Number(newRatio.toString())) / expectedRatio;
|
|
15810
|
+
logger.verbose(
|
|
15811
|
+
`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => isWithInTolerance: ${isWithInTolerance}, currentPrecision: ${currentPrecision.toString()}, expectedPrecision: ${expectedPrecision}`
|
|
15812
|
+
);
|
|
15813
|
+
if (!isWithInTolerance) {
|
|
15814
|
+
const expectedAmountsNew = await this._solveExpectedAmountsEq(
|
|
15767
15815
|
token0Bal,
|
|
15768
15816
|
token1Bal,
|
|
15769
15817
|
new Web3Number(Number(expectedRatio).toFixed(13), 18),
|
|
15770
15818
|
Number(swapPrice.toString())
|
|
15771
15819
|
);
|
|
15772
15820
|
logger.verbose(
|
|
15773
|
-
`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => expectedAmounts: ${
|
|
15821
|
+
`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => expectedAmounts: ${expectedAmountsNew.amount0.toString()}, ${expectedAmountsNew.amount1.toString()}`
|
|
15774
15822
|
);
|
|
15823
|
+
if (expectedAmountsNew.amount0.eq(expectedAmounts.amount0.toString()) && expectedAmountsNew.amount1.eq(expectedAmounts.amount1.toString())) {
|
|
15824
|
+
logger.error(
|
|
15825
|
+
`getSwapInfoGivenAmounts: stuck in loop, expected amounts did not change`
|
|
15826
|
+
);
|
|
15827
|
+
throw new Error("Stuck in loop, expected amounts did not change");
|
|
15828
|
+
}
|
|
15829
|
+
expectedAmounts = expectedAmountsNew;
|
|
15775
15830
|
} else {
|
|
15776
15831
|
const minAmountOut = Web3Number.fromWei(
|
|
15777
15832
|
quote.buyAmount.toString(),
|
|
15778
15833
|
tokenToBuyInfo.decimals
|
|
15779
15834
|
).multipliedBy(0.9999);
|
|
15780
|
-
|
|
15835
|
+
const output = await this.avnu.getSwapInfo(
|
|
15781
15836
|
quote,
|
|
15782
15837
|
this.address.address,
|
|
15783
15838
|
0,
|
|
15784
15839
|
this.address.address,
|
|
15785
15840
|
minAmountOut.toWei()
|
|
15786
15841
|
);
|
|
15842
|
+
logger.verbose(
|
|
15843
|
+
`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => swap info found: ${JSON.stringify(output)}`
|
|
15844
|
+
);
|
|
15845
|
+
return output;
|
|
15787
15846
|
}
|
|
15788
|
-
retry++;
|
|
15789
15847
|
}
|
|
15790
15848
|
throw new Error("Failed to get swap info");
|
|
15791
15849
|
}
|
|
@@ -15802,8 +15860,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15802
15860
|
* @returns Array of contract calls needed for rebalancing
|
|
15803
15861
|
* @throws Error if max retries reached without successful rebalance
|
|
15804
15862
|
*/
|
|
15805
|
-
async rebalanceIter(swapInfo, acc, estimateCall, isSellTokenToken0 = true, retry = 0, lowerLimit = 0n, upperLimit = 0n) {
|
|
15806
|
-
const MAX_RETRIES = 40;
|
|
15863
|
+
async rebalanceIter(swapInfo, acc, estimateCall, isSellTokenToken0 = true, retry = 0, lowerLimit = 0n, upperLimit = 0n, MAX_RETRIES = 40) {
|
|
15807
15864
|
logger.verbose(
|
|
15808
15865
|
`Rebalancing ${this.metadata.name}: retry=${retry}, lowerLimit=${lowerLimit}, upperLimit=${upperLimit}, isSellTokenToken0=${isSellTokenToken0}`
|
|
15809
15866
|
);
|
|
@@ -15956,7 +16013,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15956
16013
|
amount1
|
|
15957
16014
|
};
|
|
15958
16015
|
}
|
|
15959
|
-
async harvest(acc) {
|
|
16016
|
+
async harvest(acc, maxIterations = 20, priceRatioPrecision = 4) {
|
|
15960
16017
|
const ekuboHarvests = new EkuboHarvests(this.config);
|
|
15961
16018
|
const unClaimedRewards = await ekuboHarvests.getUnHarvestedRewards(
|
|
15962
16019
|
this.address
|
|
@@ -15985,7 +16042,9 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15985
16042
|
poolKey,
|
|
15986
16043
|
token0Amt,
|
|
15987
16044
|
token1Amt,
|
|
15988
|
-
bounds
|
|
16045
|
+
bounds,
|
|
16046
|
+
maxIterations,
|
|
16047
|
+
priceRatioPrecision
|
|
15989
16048
|
);
|
|
15990
16049
|
swapInfo.token_to_address = token0Info.address.address;
|
|
15991
16050
|
logger.verbose(
|
package/dist/index.mjs
CHANGED
|
@@ -1952,14 +1952,16 @@ var AvnuWrapper = class _AvnuWrapper {
|
|
|
1952
1952
|
};
|
|
1953
1953
|
assert(fromToken != toToken, "From and to tokens are the same");
|
|
1954
1954
|
const quotes = await fetchQuotes(params);
|
|
1955
|
-
|
|
1955
|
+
const filteredQuotes = quotes.filter((q) => q.sellAmount.toString() == amountWei);
|
|
1956
|
+
if (filteredQuotes.length == 0) {
|
|
1956
1957
|
if (retry < MAX_RETRY) {
|
|
1957
1958
|
await new Promise((res) => setTimeout(res, 3e3));
|
|
1958
1959
|
return await this.getQuotes(fromToken, toToken, amountWei, taker, retry + 1);
|
|
1959
1960
|
}
|
|
1960
1961
|
throw new Error("no quotes found");
|
|
1961
1962
|
}
|
|
1962
|
-
|
|
1963
|
+
logger.verbose(`${_AvnuWrapper.name}: getQuotes => Found ${JSON.stringify(filteredQuotes[0])}`);
|
|
1964
|
+
return filteredQuotes[0];
|
|
1963
1965
|
}
|
|
1964
1966
|
async getSwapInfo(quote, taker, integratorFeeBps, integratorFeeRecipient, minAmount) {
|
|
1965
1967
|
const calldata = await fetchBuildExecuteTransaction(quote.quoteId);
|
|
@@ -1982,6 +1984,7 @@ var AvnuWrapper = class _AvnuWrapper {
|
|
|
1982
1984
|
startIndex += 5 + swap_params_len;
|
|
1983
1985
|
}
|
|
1984
1986
|
const _minAmount = minAmount || (quote.buyAmount * 95n / 100n).toString();
|
|
1987
|
+
logger.verbose(`${_AvnuWrapper.name}: getSwapInfo => sellToken: ${quote.sellTokenAddress}, sellAmount: ${quote.sellAmount}`);
|
|
1985
1988
|
logger.verbose(`${_AvnuWrapper.name}: getSwapInfo => buyToken: ${quote.buyTokenAddress}`);
|
|
1986
1989
|
logger.verbose(`${_AvnuWrapper.name}: getSwapInfo => buyAmount: ${quote.buyAmount}, minAmount: ${_minAmount}`);
|
|
1987
1990
|
const swapInfo = {
|
|
@@ -15487,7 +15490,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15487
15490
|
};
|
|
15488
15491
|
}
|
|
15489
15492
|
}
|
|
15490
|
-
const ratioWeb3Number =
|
|
15493
|
+
const ratioWeb3Number = this.getRatio(sampleAmount0, sampleAmount1);
|
|
15491
15494
|
const ratio = Number(ratioWeb3Number.toFixed(18));
|
|
15492
15495
|
logger.verbose(
|
|
15493
15496
|
`${_EkuboCLVault.name}: ${this.metadata.name} => ratio: ${ratio.toString()}`
|
|
@@ -15519,11 +15522,18 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15519
15522
|
);
|
|
15520
15523
|
}
|
|
15521
15524
|
}
|
|
15525
|
+
getRatio(token0Amt, token1Amount) {
|
|
15526
|
+
const ratio = token0Amt.multipliedBy(1e18).dividedBy(token1Amount.toString()).dividedBy(1e18);
|
|
15527
|
+
logger.verbose(
|
|
15528
|
+
`${_EkuboCLVault.name}: getRatio => token0Amt: ${token0Amt.toString()}, token1Amount: ${token1Amount.toString()}, ratio: ${ratio.toString()}`
|
|
15529
|
+
);
|
|
15530
|
+
return ratio;
|
|
15531
|
+
}
|
|
15522
15532
|
_solveExpectedAmountsEq(availableAmount0, availableAmount1, ratio, price) {
|
|
15523
15533
|
const y = ratio.multipliedBy(availableAmount1).minus(availableAmount0).dividedBy(ratio.plus(1 / price));
|
|
15524
15534
|
const x = y.dividedBy(price);
|
|
15525
15535
|
logger.verbose(
|
|
15526
|
-
`${_EkuboCLVault.name}: _solveExpectedAmountsEq => x: ${x.toString()}, y: ${y.toString()}, amount0: ${availableAmount0.toString()}, amount1: ${availableAmount1.toString()}`
|
|
15536
|
+
`${_EkuboCLVault.name}: _solveExpectedAmountsEq => ratio: ${ratio.toString()}, x: ${x.toString()}, y: ${y.toString()}, amount0: ${availableAmount0.toString()}, amount1: ${availableAmount1.toString()}`
|
|
15527
15537
|
);
|
|
15528
15538
|
if (ratio.eq(0)) {
|
|
15529
15539
|
return {
|
|
@@ -15573,7 +15583,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15573
15583
|
}
|
|
15574
15584
|
};
|
|
15575
15585
|
}
|
|
15576
|
-
async getSwapInfoToHandleUnused(considerRebalance = true, newBounds = null) {
|
|
15586
|
+
async getSwapInfoToHandleUnused(considerRebalance = true, newBounds = null, maxIterations = 20, priceRatioPrecision = 4) {
|
|
15577
15587
|
const poolKey = await this.getPoolKey();
|
|
15578
15588
|
const unusedBalances = await this.unusedBalances(poolKey);
|
|
15579
15589
|
const { amount: token0Bal1, usdValue: token0PriceUsd } = unusedBalances.token0;
|
|
@@ -15606,14 +15616,59 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15606
15616
|
logger.verbose(
|
|
15607
15617
|
`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => newBounds: ${ekuboBounds.lowerTick}, ${ekuboBounds.upperTick}`
|
|
15608
15618
|
);
|
|
15619
|
+
this.assertValidBounds(ekuboBounds);
|
|
15609
15620
|
return await this.getSwapInfoGivenAmounts(
|
|
15610
15621
|
poolKey,
|
|
15611
15622
|
token0Bal,
|
|
15612
15623
|
token1Bal,
|
|
15613
|
-
ekuboBounds
|
|
15624
|
+
ekuboBounds,
|
|
15625
|
+
maxIterations,
|
|
15626
|
+
priceRatioPrecision
|
|
15627
|
+
);
|
|
15628
|
+
}
|
|
15629
|
+
assertValidBounds(bounds) {
|
|
15630
|
+
assert(
|
|
15631
|
+
bounds.lowerTick < bounds.upperTick,
|
|
15632
|
+
`Invalid bounds: lowerTick (${bounds.lowerTick}) must be less than upperTick (${bounds.upperTick})`
|
|
15614
15633
|
);
|
|
15634
|
+
assert(Number(bounds.lowerTick) % Number(this.poolKey?.tick_spacing) === 0, `Lower tick (${bounds.lowerTick}) must be a multiple of tick spacing (${this.poolKey?.tick_spacing})`);
|
|
15635
|
+
assert(Number(bounds.upperTick) % Number(this.poolKey?.tick_spacing) === 0, `Upper tick (${bounds.upperTick}) must be a multiple of tick spacing (${this.poolKey?.tick_spacing})`);
|
|
15615
15636
|
}
|
|
15616
|
-
|
|
15637
|
+
// Helper to check for invalid states:
|
|
15638
|
+
// Throws if both tokens are decreased or both are increased, which is not expected
|
|
15639
|
+
assertValidAmounts(expectedAmounts, token0Bal, token1Bal) {
|
|
15640
|
+
if (expectedAmounts.amount0.lessThan(token0Bal) && expectedAmounts.amount1.lessThan(token1Bal)) {
|
|
15641
|
+
throw new Error("Both tokens are decreased, something is wrong");
|
|
15642
|
+
}
|
|
15643
|
+
if (expectedAmounts.amount0.greaterThan(token0Bal) && expectedAmounts.amount1.greaterThan(token1Bal)) {
|
|
15644
|
+
throw new Error("Both tokens are increased, something is wrong");
|
|
15645
|
+
}
|
|
15646
|
+
}
|
|
15647
|
+
// Helper to determine which token to sell, which to buy, and the amounts to use
|
|
15648
|
+
getSwapParams(expectedAmounts, poolKey, token0Bal, token1Bal) {
|
|
15649
|
+
const tokenToSell = expectedAmounts.amount0.lessThan(token0Bal) ? poolKey.token0 : poolKey.token1;
|
|
15650
|
+
const tokenToBuy = tokenToSell == poolKey.token0 ? poolKey.token1 : poolKey.token0;
|
|
15651
|
+
const amountToSell = tokenToSell == poolKey.token0 ? token0Bal.minus(expectedAmounts.amount0) : token1Bal.minus(expectedAmounts.amount1);
|
|
15652
|
+
if (amountToSell.eq(0)) {
|
|
15653
|
+
throw new Error(
|
|
15654
|
+
`No amount to sell for ${tokenToSell.address}`
|
|
15655
|
+
);
|
|
15656
|
+
}
|
|
15657
|
+
const remainingSellAmount = tokenToSell == poolKey.token0 ? expectedAmounts.amount0 : expectedAmounts.amount1;
|
|
15658
|
+
return { tokenToSell, tokenToBuy, amountToSell, remainingSellAmount };
|
|
15659
|
+
}
|
|
15660
|
+
/**
|
|
15661
|
+
* @description Calculates swap info based on given amounts of token0 and token1
|
|
15662
|
+
* Use token0 and token1 balances to determine the expected amounts for new bounds
|
|
15663
|
+
* @param poolKey
|
|
15664
|
+
* @param token0Bal
|
|
15665
|
+
* @param token1Bal
|
|
15666
|
+
* @param bounds // new bounds
|
|
15667
|
+
* @param maxIterations
|
|
15668
|
+
* @returns {Promise<SwapInfo>}
|
|
15669
|
+
*
|
|
15670
|
+
*/
|
|
15671
|
+
async getSwapInfoGivenAmounts(poolKey, token0Bal, token1Bal, bounds, maxIterations = 20, priceRatioPrecision = 4) {
|
|
15617
15672
|
logger.verbose(
|
|
15618
15673
|
`${_EkuboCLVault.name}: getSwapInfoGivenAmounts::pre => token0Bal: ${token0Bal.toString()}, token1Bal: ${token1Bal.toString()}`
|
|
15619
15674
|
);
|
|
@@ -15626,29 +15681,14 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15626
15681
|
`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => expectedAmounts2: ${expectedAmounts.amount0.toString()}, ${expectedAmounts.amount1.toString()}`
|
|
15627
15682
|
);
|
|
15628
15683
|
let retry = 0;
|
|
15629
|
-
const maxRetry =
|
|
15630
|
-
function assertValidAmounts(expectedAmounts2, token0Bal2, token1Bal2) {
|
|
15631
|
-
if (expectedAmounts2.amount0.lessThan(token0Bal2) && expectedAmounts2.amount1.lessThan(token1Bal2)) {
|
|
15632
|
-
throw new Error("Both tokens are decreased, something is wrong");
|
|
15633
|
-
}
|
|
15634
|
-
if (expectedAmounts2.amount0.greaterThan(token0Bal2) && expectedAmounts2.amount1.greaterThan(token1Bal2)) {
|
|
15635
|
-
throw new Error("Both tokens are increased, something is wrong");
|
|
15636
|
-
}
|
|
15637
|
-
}
|
|
15638
|
-
function getSwapParams(expectedAmounts2, poolKey2, token0Bal2, token1Bal2) {
|
|
15639
|
-
const tokenToSell = expectedAmounts2.amount0.lessThan(token0Bal2) ? poolKey2.token0 : poolKey2.token1;
|
|
15640
|
-
const tokenToBuy = tokenToSell == poolKey2.token0 ? poolKey2.token1 : poolKey2.token0;
|
|
15641
|
-
const amountToSell = tokenToSell == poolKey2.token0 ? token0Bal2.minus(expectedAmounts2.amount0) : token1Bal2.minus(expectedAmounts2.amount1);
|
|
15642
|
-
const remainingSellAmount = tokenToSell == poolKey2.token0 ? expectedAmounts2.amount0 : expectedAmounts2.amount1;
|
|
15643
|
-
return { tokenToSell, tokenToBuy, amountToSell, remainingSellAmount };
|
|
15644
|
-
}
|
|
15684
|
+
const maxRetry = maxIterations;
|
|
15645
15685
|
while (retry < maxRetry) {
|
|
15646
15686
|
retry++;
|
|
15647
15687
|
logger.verbose(
|
|
15648
15688
|
`getSwapInfoGivenAmounts::Retry attempt: ${retry}/${maxRetry}`
|
|
15649
15689
|
);
|
|
15650
|
-
assertValidAmounts(expectedAmounts, token0Bal, token1Bal);
|
|
15651
|
-
const { tokenToSell, tokenToBuy, amountToSell, remainingSellAmount } = getSwapParams(expectedAmounts, poolKey, token0Bal, token1Bal);
|
|
15690
|
+
this.assertValidAmounts(expectedAmounts, token0Bal, token1Bal);
|
|
15691
|
+
const { tokenToSell, tokenToBuy, amountToSell, remainingSellAmount } = this.getSwapParams(expectedAmounts, poolKey, token0Bal, token1Bal);
|
|
15652
15692
|
const tokenToBuyInfo = await Global.getTokenInfoFromAddr(tokenToBuy);
|
|
15653
15693
|
const expectedRatio = expectedAmounts.ratio;
|
|
15654
15694
|
logger.verbose(
|
|
@@ -15686,6 +15726,9 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15686
15726
|
quote.buyAmount.toString(),
|
|
15687
15727
|
tokenToBuyInfo.decimals
|
|
15688
15728
|
);
|
|
15729
|
+
logger.verbose(
|
|
15730
|
+
`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => amountOut: ${amountOut.toString()}`
|
|
15731
|
+
);
|
|
15689
15732
|
const swapPrice = tokenToSell == poolKey.token0 ? amountOut.dividedBy(amountToSell) : amountToSell.dividedBy(amountOut);
|
|
15690
15733
|
const newRatio = tokenToSell == poolKey.token0 ? remainingSellAmount.dividedBy(token1Bal.plus(amountOut)) : token0Bal.plus(amountOut).dividedBy(remainingSellAmount);
|
|
15691
15734
|
logger.verbose(
|
|
@@ -15695,31 +15738,46 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15695
15738
|
newRatio: newRatio.toString()
|
|
15696
15739
|
})}`
|
|
15697
15740
|
);
|
|
15698
|
-
const expectedPrecision = Math.min(
|
|
15699
|
-
|
|
15700
|
-
|
|
15741
|
+
const expectedPrecision = Math.min(priceRatioPrecision);
|
|
15742
|
+
const isWithInTolerance = Number(newRatio.toString()) <= expectedRatio * (1 + 1 / 10 ** expectedPrecision) && Number(newRatio.toString()) >= expectedRatio * (1 - 1 / 10 ** expectedPrecision);
|
|
15743
|
+
const currentPrecision = (expectedRatio - Number(newRatio.toString())) / expectedRatio;
|
|
15744
|
+
logger.verbose(
|
|
15745
|
+
`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => isWithInTolerance: ${isWithInTolerance}, currentPrecision: ${currentPrecision.toString()}, expectedPrecision: ${expectedPrecision}`
|
|
15746
|
+
);
|
|
15747
|
+
if (!isWithInTolerance) {
|
|
15748
|
+
const expectedAmountsNew = await this._solveExpectedAmountsEq(
|
|
15701
15749
|
token0Bal,
|
|
15702
15750
|
token1Bal,
|
|
15703
15751
|
new Web3Number(Number(expectedRatio).toFixed(13), 18),
|
|
15704
15752
|
Number(swapPrice.toString())
|
|
15705
15753
|
);
|
|
15706
15754
|
logger.verbose(
|
|
15707
|
-
`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => expectedAmounts: ${
|
|
15755
|
+
`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => expectedAmounts: ${expectedAmountsNew.amount0.toString()}, ${expectedAmountsNew.amount1.toString()}`
|
|
15708
15756
|
);
|
|
15757
|
+
if (expectedAmountsNew.amount0.eq(expectedAmounts.amount0.toString()) && expectedAmountsNew.amount1.eq(expectedAmounts.amount1.toString())) {
|
|
15758
|
+
logger.error(
|
|
15759
|
+
`getSwapInfoGivenAmounts: stuck in loop, expected amounts did not change`
|
|
15760
|
+
);
|
|
15761
|
+
throw new Error("Stuck in loop, expected amounts did not change");
|
|
15762
|
+
}
|
|
15763
|
+
expectedAmounts = expectedAmountsNew;
|
|
15709
15764
|
} else {
|
|
15710
15765
|
const minAmountOut = Web3Number.fromWei(
|
|
15711
15766
|
quote.buyAmount.toString(),
|
|
15712
15767
|
tokenToBuyInfo.decimals
|
|
15713
15768
|
).multipliedBy(0.9999);
|
|
15714
|
-
|
|
15769
|
+
const output = await this.avnu.getSwapInfo(
|
|
15715
15770
|
quote,
|
|
15716
15771
|
this.address.address,
|
|
15717
15772
|
0,
|
|
15718
15773
|
this.address.address,
|
|
15719
15774
|
minAmountOut.toWei()
|
|
15720
15775
|
);
|
|
15776
|
+
logger.verbose(
|
|
15777
|
+
`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => swap info found: ${JSON.stringify(output)}`
|
|
15778
|
+
);
|
|
15779
|
+
return output;
|
|
15721
15780
|
}
|
|
15722
|
-
retry++;
|
|
15723
15781
|
}
|
|
15724
15782
|
throw new Error("Failed to get swap info");
|
|
15725
15783
|
}
|
|
@@ -15736,8 +15794,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15736
15794
|
* @returns Array of contract calls needed for rebalancing
|
|
15737
15795
|
* @throws Error if max retries reached without successful rebalance
|
|
15738
15796
|
*/
|
|
15739
|
-
async rebalanceIter(swapInfo, acc, estimateCall, isSellTokenToken0 = true, retry = 0, lowerLimit = 0n, upperLimit = 0n) {
|
|
15740
|
-
const MAX_RETRIES = 40;
|
|
15797
|
+
async rebalanceIter(swapInfo, acc, estimateCall, isSellTokenToken0 = true, retry = 0, lowerLimit = 0n, upperLimit = 0n, MAX_RETRIES = 40) {
|
|
15741
15798
|
logger.verbose(
|
|
15742
15799
|
`Rebalancing ${this.metadata.name}: retry=${retry}, lowerLimit=${lowerLimit}, upperLimit=${upperLimit}, isSellTokenToken0=${isSellTokenToken0}`
|
|
15743
15800
|
);
|
|
@@ -15890,7 +15947,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15890
15947
|
amount1
|
|
15891
15948
|
};
|
|
15892
15949
|
}
|
|
15893
|
-
async harvest(acc) {
|
|
15950
|
+
async harvest(acc, maxIterations = 20, priceRatioPrecision = 4) {
|
|
15894
15951
|
const ekuboHarvests = new EkuboHarvests(this.config);
|
|
15895
15952
|
const unClaimedRewards = await ekuboHarvests.getUnHarvestedRewards(
|
|
15896
15953
|
this.address
|
|
@@ -15919,7 +15976,9 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
15919
15976
|
poolKey,
|
|
15920
15977
|
token0Amt,
|
|
15921
15978
|
token1Amt,
|
|
15922
|
-
bounds
|
|
15979
|
+
bounds,
|
|
15980
|
+
maxIterations,
|
|
15981
|
+
priceRatioPrecision
|
|
15923
15982
|
);
|
|
15924
15983
|
swapInfo.token_to_address = token0Info.address.address;
|
|
15925
15984
|
logger.verbose(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strkfarm/sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.53",
|
|
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",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"@types/react": "^19.1.2",
|
|
45
45
|
"jest": "^29.7.0",
|
|
46
46
|
"jest-environment-jsdom": "^29.7.0",
|
|
47
|
+
"request": "^2.88.2",
|
|
47
48
|
"ts-jest": "^29.1.5",
|
|
48
49
|
"ts-node": "^10.9.2",
|
|
49
50
|
"tsup": "^8.1.0",
|
|
@@ -51,9 +52,9 @@
|
|
|
51
52
|
"typescript": "^5.5.3"
|
|
52
53
|
},
|
|
53
54
|
"peerDependencies": {
|
|
55
|
+
"axios": "^1.7.2",
|
|
54
56
|
"react": "19.1.0",
|
|
55
|
-
"starknet": "^6.11.0"
|
|
56
|
-
"axios": "^1.7.2"
|
|
57
|
+
"starknet": "^6.11.0"
|
|
57
58
|
},
|
|
58
59
|
"dependencies": {
|
|
59
60
|
"@avnu/avnu-sdk": "3.0.2",
|
package/src/modules/avnu.ts
CHANGED
|
@@ -50,14 +50,18 @@ export class AvnuWrapper {
|
|
|
50
50
|
assert(fromToken != toToken, "From and to tokens are the same");
|
|
51
51
|
|
|
52
52
|
const quotes = await fetchQuotes(params);
|
|
53
|
-
|
|
53
|
+
// precision is important for us
|
|
54
|
+
const filteredQuotes = quotes.filter((q) => q.sellAmount.toString() == amountWei);
|
|
55
|
+
if (filteredQuotes.length == 0) {
|
|
54
56
|
if (retry < MAX_RETRY) {
|
|
55
57
|
await new Promise((res) => setTimeout(res, 3000))
|
|
56
58
|
return await this.getQuotes(fromToken, toToken, amountWei, taker, retry + 1);
|
|
57
59
|
}
|
|
58
60
|
throw new Error('no quotes found')
|
|
59
61
|
}
|
|
60
|
-
|
|
62
|
+
|
|
63
|
+
logger.verbose(`${AvnuWrapper.name}: getQuotes => Found ${JSON.stringify(filteredQuotes[0])}`);
|
|
64
|
+
return filteredQuotes[0];
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
async getSwapInfo(
|
|
@@ -96,6 +100,7 @@ export class AvnuWrapper {
|
|
|
96
100
|
// swapInfo as expected by the strategy
|
|
97
101
|
// fallback, max 1% slippage
|
|
98
102
|
const _minAmount = minAmount || (quote.buyAmount * 95n / 100n).toString();
|
|
103
|
+
logger.verbose(`${AvnuWrapper.name}: getSwapInfo => sellToken: ${quote.sellTokenAddress}, sellAmount: ${quote.sellAmount}`);
|
|
99
104
|
logger.verbose(`${AvnuWrapper.name}: getSwapInfo => buyToken: ${quote.buyTokenAddress}`);
|
|
100
105
|
logger.verbose(`${AvnuWrapper.name}: getSwapInfo => buyAmount: ${quote.buyAmount}, minAmount: ${_minAmount}`);
|
|
101
106
|
const swapInfo: SwapInfo = {
|