@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.
@@ -745,10 +745,7 @@ export class EkuboCLVault extends BaseStrategy<
745
745
  }
746
746
  }
747
747
 
748
- const ratioWeb3Number = sampleAmount0
749
- .multipliedBy(1e18)
750
- .dividedBy(sampleAmount1.toString())
751
- .dividedBy(1e18);
748
+ const ratioWeb3Number = this.getRatio(sampleAmount0, sampleAmount1);
752
749
 
753
750
  const ratio: number = Number(ratioWeb3Number.toFixed(18));
754
751
  logger.verbose(
@@ -793,6 +790,17 @@ export class EkuboCLVault extends BaseStrategy<
793
790
  }
794
791
  }
795
792
 
793
+ getRatio(token0Amt: Web3Number, token1Amount: Web3Number) {
794
+ const ratio = token0Amt
795
+ .multipliedBy(1e18)
796
+ .dividedBy(token1Amount.toString())
797
+ .dividedBy(1e18);
798
+ logger.verbose(
799
+ `${EkuboCLVault.name}: getRatio => token0Amt: ${token0Amt.toString()}, token1Amount: ${token1Amount.toString()}, ratio: ${ratio.toString()}`
800
+ );
801
+ return ratio;
802
+ }
803
+
796
804
  private _solveExpectedAmountsEq(
797
805
  availableAmount0: Web3Number,
798
806
  availableAmount1: Web3Number,
@@ -814,7 +822,7 @@ export class EkuboCLVault extends BaseStrategy<
814
822
  logger.verbose(
815
823
  `${
816
824
  EkuboCLVault.name
817
- }: _solveExpectedAmountsEq => x: ${x.toString()}, y: ${y.toString()}, amount0: ${availableAmount0.toString()}, amount1: ${availableAmount1.toString()}`
825
+ }: _solveExpectedAmountsEq => ratio: ${ratio.toString()}, x: ${x.toString()}, y: ${y.toString()}, amount0: ${availableAmount0.toString()}, amount1: ${availableAmount1.toString()}`
818
826
  );
819
827
 
820
828
  if (ratio.eq(0)) {
@@ -872,7 +880,7 @@ export class EkuboCLVault extends BaseStrategy<
872
880
  };
873
881
  }
874
882
 
875
- async getSwapInfoToHandleUnused(considerRebalance: boolean = true, newBounds: EkuboBounds | null = null): Promise<SwapInfo> {
883
+ async getSwapInfoToHandleUnused(considerRebalance: boolean = true, newBounds: EkuboBounds | null = null, maxIterations = 20, priceRatioPrecision = 4): Promise<SwapInfo> {
876
884
  const poolKey = await this.getPoolKey();
877
885
 
878
886
  // fetch current unused balances of vault
@@ -928,19 +936,100 @@ export class EkuboCLVault extends BaseStrategy<
928
936
  `${EkuboCLVault.name}: getSwapInfoToHandleUnused => newBounds: ${ekuboBounds.lowerTick}, ${ekuboBounds.upperTick}`
929
937
  );
930
938
 
939
+ // assert bounds are valid
940
+ this.assertValidBounds(ekuboBounds);
941
+
931
942
  return await this.getSwapInfoGivenAmounts(
932
943
  poolKey,
933
944
  token0Bal,
934
945
  token1Bal,
935
- ekuboBounds
946
+ ekuboBounds,
947
+ maxIterations,
948
+ priceRatioPrecision
936
949
  );
937
950
  }
938
951
 
952
+ assertValidBounds(bounds: EkuboBounds) {
953
+ // Ensure bounds are valid
954
+ assert(
955
+ bounds.lowerTick < bounds.upperTick,
956
+ `Invalid bounds: lowerTick (${bounds.lowerTick}) must be less than upperTick (${bounds.upperTick})`
957
+ );
958
+ 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})`);
959
+ 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})`);
960
+ }
961
+
962
+ // Helper to check for invalid states:
963
+ // Throws if both tokens are decreased or both are increased, which is not expected
964
+ assertValidAmounts(
965
+ expectedAmounts: any,
966
+ token0Bal: Web3Number,
967
+ token1Bal: Web3Number
968
+ ) {
969
+ if (
970
+ expectedAmounts.amount0.lessThan(token0Bal) &&
971
+ expectedAmounts.amount1.lessThan(token1Bal)
972
+ ) {
973
+ throw new Error("Both tokens are decreased, something is wrong");
974
+ }
975
+ if (
976
+ expectedAmounts.amount0.greaterThan(token0Bal) &&
977
+ expectedAmounts.amount1.greaterThan(token1Bal)
978
+ ) {
979
+ throw new Error("Both tokens are increased, something is wrong");
980
+ }
981
+ }
982
+
983
+ // Helper to determine which token to sell, which to buy, and the amounts to use
984
+ getSwapParams(
985
+ expectedAmounts: any,
986
+ poolKey: EkuboPoolKey,
987
+ token0Bal: Web3Number,
988
+ token1Bal: Web3Number
989
+ ) {
990
+ // Decide which token to sell based on which expected amount is less than the balance
991
+ const tokenToSell = expectedAmounts.amount0.lessThan(token0Bal)
992
+ ? poolKey.token0
993
+ : poolKey.token1;
994
+ // The other token is the one to buy
995
+ const tokenToBuy =
996
+ tokenToSell == poolKey.token0 ? poolKey.token1 : poolKey.token0;
997
+ // Calculate how much to sell
998
+ const amountToSell =
999
+ tokenToSell == poolKey.token0
1000
+ ? token0Bal.minus(expectedAmounts.amount0)
1001
+ : token1Bal.minus(expectedAmounts.amount1);
1002
+ if (amountToSell.eq(0)) {
1003
+ throw new Error(
1004
+ `No amount to sell for ${tokenToSell.address}`
1005
+ );
1006
+ }
1007
+ // The remaining amount of the sold token after swap
1008
+ const remainingSellAmount =
1009
+ tokenToSell == poolKey.token0
1010
+ ? expectedAmounts.amount0
1011
+ : expectedAmounts.amount1;
1012
+ return { tokenToSell, tokenToBuy, amountToSell, remainingSellAmount };
1013
+ }
1014
+
1015
+ /**
1016
+ * @description Calculates swap info based on given amounts of token0 and token1
1017
+ * Use token0 and token1 balances to determine the expected amounts for new bounds
1018
+ * @param poolKey
1019
+ * @param token0Bal
1020
+ * @param token1Bal
1021
+ * @param bounds // new bounds
1022
+ * @param maxIterations
1023
+ * @returns {Promise<SwapInfo>}
1024
+ *
1025
+ */
939
1026
  async getSwapInfoGivenAmounts(
940
1027
  poolKey: EkuboPoolKey,
941
1028
  token0Bal: Web3Number,
942
1029
  token1Bal: Web3Number,
943
- bounds: EkuboBounds
1030
+ bounds: EkuboBounds,
1031
+ maxIterations: number = 20,
1032
+ priceRatioPrecision: number = 4
944
1033
  ): Promise<SwapInfo> {
945
1034
  logger.verbose(
946
1035
  `${
@@ -961,55 +1050,7 @@ export class EkuboCLVault extends BaseStrategy<
961
1050
  );
962
1051
 
963
1052
  let retry = 0;
964
- const maxRetry = 10;
965
-
966
- // Helper to check for invalid states:
967
- // Throws if both tokens are decreased or both are increased, which is not expected
968
- function assertValidAmounts(
969
- expectedAmounts: any,
970
- token0Bal: Web3Number,
971
- token1Bal: Web3Number
972
- ) {
973
- if (
974
- expectedAmounts.amount0.lessThan(token0Bal) &&
975
- expectedAmounts.amount1.lessThan(token1Bal)
976
- ) {
977
- throw new Error("Both tokens are decreased, something is wrong");
978
- }
979
- if (
980
- expectedAmounts.amount0.greaterThan(token0Bal) &&
981
- expectedAmounts.amount1.greaterThan(token1Bal)
982
- ) {
983
- throw new Error("Both tokens are increased, something is wrong");
984
- }
985
- }
986
-
987
- // Helper to determine which token to sell, which to buy, and the amounts to use
988
- function getSwapParams(
989
- expectedAmounts: any,
990
- poolKey: EkuboPoolKey,
991
- token0Bal: Web3Number,
992
- token1Bal: Web3Number
993
- ) {
994
- // Decide which token to sell based on which expected amount is less than the balance
995
- const tokenToSell = expectedAmounts.amount0.lessThan(token0Bal)
996
- ? poolKey.token0
997
- : poolKey.token1;
998
- // The other token is the one to buy
999
- const tokenToBuy =
1000
- tokenToSell == poolKey.token0 ? poolKey.token1 : poolKey.token0;
1001
- // Calculate how much to sell
1002
- const amountToSell =
1003
- tokenToSell == poolKey.token0
1004
- ? token0Bal.minus(expectedAmounts.amount0)
1005
- : token1Bal.minus(expectedAmounts.amount1);
1006
- // The remaining amount of the sold token after swap
1007
- const remainingSellAmount =
1008
- tokenToSell == poolKey.token0
1009
- ? expectedAmounts.amount0
1010
- : expectedAmounts.amount1;
1011
- return { tokenToSell, tokenToBuy, amountToSell, remainingSellAmount };
1012
- }
1053
+ const maxRetry = maxIterations;
1013
1054
 
1014
1055
  // Main retry loop: attempts to find a swap that matches the expected ratio within tolerance
1015
1056
  while (retry < maxRetry) {
@@ -1019,11 +1060,11 @@ export class EkuboCLVault extends BaseStrategy<
1019
1060
  );
1020
1061
 
1021
1062
  // Ensure the expected amounts are valid for swap logic
1022
- assertValidAmounts(expectedAmounts, token0Bal, token1Bal);
1063
+ this.assertValidAmounts(expectedAmounts, token0Bal, token1Bal);
1023
1064
 
1024
1065
  // Get swap parameters for this iteration
1025
1066
  const { tokenToSell, tokenToBuy, amountToSell, remainingSellAmount } =
1026
- getSwapParams(expectedAmounts, poolKey, token0Bal, token1Bal);
1067
+ this.getSwapParams(expectedAmounts, poolKey, token0Bal, token1Bal);
1027
1068
 
1028
1069
  const tokenToBuyInfo = await Global.getTokenInfoFromAddr(tokenToBuy);
1029
1070
  const expectedRatio = expectedAmounts.ratio;
@@ -1065,12 +1106,16 @@ export class EkuboCLVault extends BaseStrategy<
1065
1106
  );
1066
1107
  }
1067
1108
 
1068
- // Calculate the actual output and price from the quote
1109
+ // Raw amount out
1069
1110
  const amountOut = Web3Number.fromWei(
1070
1111
  quote.buyAmount.toString(),
1071
1112
  tokenToBuyInfo.decimals
1072
1113
  );
1114
+ logger.verbose(
1115
+ `${EkuboCLVault.name}: getSwapInfoToHandleUnused => amountOut: ${amountOut.toString()}`
1116
+ );
1073
1117
  // Calculate the swap price depending on which token is being sold
1118
+ // price is token1 / token0
1074
1119
  const swapPrice =
1075
1120
  tokenToSell == poolKey.token0
1076
1121
  ? amountOut.dividedBy(amountToSell)
@@ -1089,12 +1134,19 @@ export class EkuboCLVault extends BaseStrategy<
1089
1134
  );
1090
1135
 
1091
1136
  // If the new ratio is not within tolerance, adjust expected amounts and retry
1092
- const expectedPrecision = Math.min(7, tokenToBuyInfo.decimals - 2);
1137
+ const expectedPrecision = Math.min(priceRatioPrecision); // e.g 7 for STRK, 4 for USDC
1138
+ const isWithInTolerance =
1139
+ Number(newRatio.toString()) <=
1140
+ expectedRatio * (1 + 1 / 10 ** expectedPrecision) &&
1141
+ Number(newRatio.toString()) >= expectedRatio * (1 - 1 / 10 ** expectedPrecision);
1142
+ const currentPrecision = (expectedRatio - Number(newRatio.toString())) / expectedRatio;
1143
+ logger.verbose(
1144
+ `${EkuboCLVault.name}: getSwapInfoToHandleUnused => isWithInTolerance: ${isWithInTolerance}, currentPrecision: ${currentPrecision.toString()}, expectedPrecision: ${expectedPrecision}`
1145
+ );
1093
1146
  if (
1094
- Number(newRatio.toString()) > expectedRatio * (1 + 1 / 10 ** expectedPrecision) ||
1095
- Number(newRatio.toString()) < expectedRatio * (1 - 1 / 10 ** expectedPrecision)
1147
+ !isWithInTolerance
1096
1148
  ) {
1097
- expectedAmounts = await this._solveExpectedAmountsEq(
1149
+ const expectedAmountsNew = await this._solveExpectedAmountsEq(
1098
1150
  token0Bal,
1099
1151
  token1Bal,
1100
1152
  new Web3Number(Number(expectedRatio).toFixed(13), 18),
@@ -1103,23 +1155,34 @@ export class EkuboCLVault extends BaseStrategy<
1103
1155
  logger.verbose(
1104
1156
  `${
1105
1157
  EkuboCLVault.name
1106
- }: getSwapInfoToHandleUnused => expectedAmounts: ${expectedAmounts.amount0.toString()}, ${expectedAmounts.amount1.toString()}`
1158
+ }: getSwapInfoToHandleUnused => expectedAmounts: ${expectedAmountsNew.amount0.toString()}, ${expectedAmountsNew.amount1.toString()}`
1107
1159
  );
1160
+ if (expectedAmountsNew.amount0.eq(expectedAmounts.amount0.toString()) && expectedAmountsNew.amount1.eq(expectedAmounts.amount1.toString())) {
1161
+ // If the expected amounts did not change, we are stuck in a loop
1162
+ logger.error(
1163
+ `getSwapInfoGivenAmounts: stuck in loop, expected amounts did not change`
1164
+ );
1165
+ throw new Error("Stuck in loop, expected amounts did not change");
1166
+ }
1167
+ expectedAmounts = expectedAmountsNew;
1108
1168
  } else {
1109
1169
  // Otherwise, return the swap info with a slippage buffer
1110
1170
  const minAmountOut = Web3Number.fromWei(
1111
1171
  quote.buyAmount.toString(),
1112
1172
  tokenToBuyInfo.decimals
1113
1173
  ).multipliedBy(0.9999);
1114
- return await this.avnu.getSwapInfo(
1174
+ const output = await this.avnu.getSwapInfo(
1115
1175
  quote,
1116
1176
  this.address.address,
1117
1177
  0,
1118
1178
  this.address.address,
1119
1179
  minAmountOut.toWei()
1120
1180
  );
1181
+ logger.verbose(
1182
+ `${EkuboCLVault.name}: getSwapInfoToHandleUnused => swap info found: ${JSON.stringify(output)}`
1183
+ );
1184
+ return output;
1121
1185
  }
1122
- retry++;
1123
1186
  }
1124
1187
 
1125
1188
  // If no suitable swap found after max retries, throw error
@@ -1146,9 +1209,9 @@ export class EkuboCLVault extends BaseStrategy<
1146
1209
  isSellTokenToken0 = true,
1147
1210
  retry = 0,
1148
1211
  lowerLimit = 0n,
1149
- upperLimit = 0n
1212
+ upperLimit = 0n,
1213
+ MAX_RETRIES = 40
1150
1214
  ): Promise<Call[]> {
1151
- const MAX_RETRIES = 40;
1152
1215
 
1153
1216
  logger.verbose(
1154
1217
  `Rebalancing ${this.metadata.name}: ` +
@@ -1334,7 +1397,7 @@ export class EkuboCLVault extends BaseStrategy<
1334
1397
  };
1335
1398
  }
1336
1399
 
1337
- async harvest(acc: Account) {
1400
+ async harvest(acc: Account, maxIterations = 20, priceRatioPrecision = 4): Promise<Call[]> {
1338
1401
  const ekuboHarvests = new EkuboHarvests(this.config);
1339
1402
  const unClaimedRewards = await ekuboHarvests.getUnHarvestedRewards(
1340
1403
  this.address
@@ -1377,7 +1440,9 @@ export class EkuboCLVault extends BaseStrategy<
1377
1440
  poolKey,
1378
1441
  token0Amt,
1379
1442
  token1Amt,
1380
- bounds
1443
+ bounds,
1444
+ maxIterations,
1445
+ priceRatioPrecision
1381
1446
  );
1382
1447
  swapInfo.token_to_address = token0Info.address.address;
1383
1448
  logger.verbose(