@strkfarm/sdk 1.0.52 → 1.0.54

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, maxIterations = 20): 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,21 +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
946
  ekuboBounds,
936
- maxIterations
947
+ maxIterations,
948
+ priceRatioPrecision
937
949
  );
938
950
  }
939
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
+ */
940
1026
  async getSwapInfoGivenAmounts(
941
1027
  poolKey: EkuboPoolKey,
942
1028
  token0Bal: Web3Number,
943
1029
  token1Bal: Web3Number,
944
1030
  bounds: EkuboBounds,
945
- maxIterations: number = 20
1031
+ maxIterations: number = 20,
1032
+ priceRatioPrecision: number = 4
946
1033
  ): Promise<SwapInfo> {
947
1034
  logger.verbose(
948
1035
  `${
@@ -965,54 +1052,6 @@ export class EkuboCLVault extends BaseStrategy<
965
1052
  let retry = 0;
966
1053
  const maxRetry = maxIterations;
967
1054
 
968
- // Helper to check for invalid states:
969
- // Throws if both tokens are decreased or both are increased, which is not expected
970
- function assertValidAmounts(
971
- expectedAmounts: any,
972
- token0Bal: Web3Number,
973
- token1Bal: Web3Number
974
- ) {
975
- if (
976
- expectedAmounts.amount0.lessThan(token0Bal) &&
977
- expectedAmounts.amount1.lessThan(token1Bal)
978
- ) {
979
- throw new Error("Both tokens are decreased, something is wrong");
980
- }
981
- if (
982
- expectedAmounts.amount0.greaterThan(token0Bal) &&
983
- expectedAmounts.amount1.greaterThan(token1Bal)
984
- ) {
985
- throw new Error("Both tokens are increased, something is wrong");
986
- }
987
- }
988
-
989
- // Helper to determine which token to sell, which to buy, and the amounts to use
990
- function getSwapParams(
991
- expectedAmounts: any,
992
- poolKey: EkuboPoolKey,
993
- token0Bal: Web3Number,
994
- token1Bal: Web3Number
995
- ) {
996
- // Decide which token to sell based on which expected amount is less than the balance
997
- const tokenToSell = expectedAmounts.amount0.lessThan(token0Bal)
998
- ? poolKey.token0
999
- : poolKey.token1;
1000
- // The other token is the one to buy
1001
- const tokenToBuy =
1002
- tokenToSell == poolKey.token0 ? poolKey.token1 : poolKey.token0;
1003
- // Calculate how much to sell
1004
- const amountToSell =
1005
- tokenToSell == poolKey.token0
1006
- ? token0Bal.minus(expectedAmounts.amount0)
1007
- : token1Bal.minus(expectedAmounts.amount1);
1008
- // The remaining amount of the sold token after swap
1009
- const remainingSellAmount =
1010
- tokenToSell == poolKey.token0
1011
- ? expectedAmounts.amount0
1012
- : expectedAmounts.amount1;
1013
- return { tokenToSell, tokenToBuy, amountToSell, remainingSellAmount };
1014
- }
1015
-
1016
1055
  // Main retry loop: attempts to find a swap that matches the expected ratio within tolerance
1017
1056
  while (retry < maxRetry) {
1018
1057
  retry++;
@@ -1021,11 +1060,11 @@ export class EkuboCLVault extends BaseStrategy<
1021
1060
  );
1022
1061
 
1023
1062
  // Ensure the expected amounts are valid for swap logic
1024
- assertValidAmounts(expectedAmounts, token0Bal, token1Bal);
1063
+ this.assertValidAmounts(expectedAmounts, token0Bal, token1Bal);
1025
1064
 
1026
1065
  // Get swap parameters for this iteration
1027
1066
  const { tokenToSell, tokenToBuy, amountToSell, remainingSellAmount } =
1028
- getSwapParams(expectedAmounts, poolKey, token0Bal, token1Bal);
1067
+ this.getSwapParams(expectedAmounts, poolKey, token0Bal, token1Bal);
1029
1068
 
1030
1069
  const tokenToBuyInfo = await Global.getTokenInfoFromAddr(tokenToBuy);
1031
1070
  const expectedRatio = expectedAmounts.ratio;
@@ -1067,12 +1106,16 @@ export class EkuboCLVault extends BaseStrategy<
1067
1106
  );
1068
1107
  }
1069
1108
 
1070
- // Calculate the actual output and price from the quote
1109
+ // Raw amount out
1071
1110
  const amountOut = Web3Number.fromWei(
1072
1111
  quote.buyAmount.toString(),
1073
1112
  tokenToBuyInfo.decimals
1074
1113
  );
1114
+ logger.verbose(
1115
+ `${EkuboCLVault.name}: getSwapInfoToHandleUnused => amountOut: ${amountOut.toString()}`
1116
+ );
1075
1117
  // Calculate the swap price depending on which token is being sold
1118
+ // price is token1 / token0
1076
1119
  const swapPrice =
1077
1120
  tokenToSell == poolKey.token0
1078
1121
  ? amountOut.dividedBy(amountToSell)
@@ -1091,12 +1134,19 @@ export class EkuboCLVault extends BaseStrategy<
1091
1134
  );
1092
1135
 
1093
1136
  // If the new ratio is not within tolerance, adjust expected amounts and retry
1094
- 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
+ );
1095
1146
  if (
1096
- Number(newRatio.toString()) > expectedRatio * (1 + 1 / 10 ** expectedPrecision) ||
1097
- Number(newRatio.toString()) < expectedRatio * (1 - 1 / 10 ** expectedPrecision)
1147
+ !isWithInTolerance
1098
1148
  ) {
1099
- expectedAmounts = await this._solveExpectedAmountsEq(
1149
+ const expectedAmountsNew = await this._solveExpectedAmountsEq(
1100
1150
  token0Bal,
1101
1151
  token1Bal,
1102
1152
  new Web3Number(Number(expectedRatio).toFixed(13), 18),
@@ -1105,23 +1155,34 @@ export class EkuboCLVault extends BaseStrategy<
1105
1155
  logger.verbose(
1106
1156
  `${
1107
1157
  EkuboCLVault.name
1108
- }: getSwapInfoToHandleUnused => expectedAmounts: ${expectedAmounts.amount0.toString()}, ${expectedAmounts.amount1.toString()}`
1158
+ }: getSwapInfoToHandleUnused => expectedAmounts: ${expectedAmountsNew.amount0.toString()}, ${expectedAmountsNew.amount1.toString()}`
1109
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;
1110
1168
  } else {
1111
1169
  // Otherwise, return the swap info with a slippage buffer
1112
1170
  const minAmountOut = Web3Number.fromWei(
1113
1171
  quote.buyAmount.toString(),
1114
1172
  tokenToBuyInfo.decimals
1115
1173
  ).multipliedBy(0.9999);
1116
- return await this.avnu.getSwapInfo(
1174
+ const output = await this.avnu.getSwapInfo(
1117
1175
  quote,
1118
1176
  this.address.address,
1119
1177
  0,
1120
1178
  this.address.address,
1121
1179
  minAmountOut.toWei()
1122
1180
  );
1181
+ logger.verbose(
1182
+ `${EkuboCLVault.name}: getSwapInfoToHandleUnused => swap info found: ${JSON.stringify(output)}`
1183
+ );
1184
+ return output;
1123
1185
  }
1124
- retry++;
1125
1186
  }
1126
1187
 
1127
1188
  // If no suitable swap found after max retries, throw error
@@ -1336,7 +1397,7 @@ export class EkuboCLVault extends BaseStrategy<
1336
1397
  };
1337
1398
  }
1338
1399
 
1339
- async harvest(acc: Account, maxIterations = 20): Promise<Call[]> {
1400
+ async harvest(acc: Account, maxIterations = 20, priceRatioPrecision = 4): Promise<Call[]> {
1340
1401
  const ekuboHarvests = new EkuboHarvests(this.config);
1341
1402
  const unClaimedRewards = await ekuboHarvests.getUnHarvestedRewards(
1342
1403
  this.address
@@ -1380,7 +1441,8 @@ export class EkuboCLVault extends BaseStrategy<
1380
1441
  token0Amt,
1381
1442
  token1Amt,
1382
1443
  bounds,
1383
- maxIterations
1444
+ maxIterations,
1445
+ priceRatioPrecision
1384
1446
  );
1385
1447
  swapInfo.token_to_address = token0Info.address.address;
1386
1448
  logger.verbose(
@@ -1394,11 +1456,19 @@ export class EkuboCLVault extends BaseStrategy<
1394
1456
  const swap1Amount = Web3Number.fromWei(
1395
1457
  uint256.uint256ToBN(swapInfo1.token_from_amount).toString(),
1396
1458
  18 // cause its always STRK?
1397
- );
1459
+ ).minimum(
1460
+ postFeeAmount.toFixed(18) // cause always strk
1461
+ ); // ensure we don't swap more than we have
1462
+ swapInfo.token_from_amount = uint256.bnToUint256(swap1Amount.toWei());
1463
+ swapInfo.token_to_min_amount = uint256.bnToUint256(
1464
+ swap1Amount.multipliedBy(0).toWei() // placeholder
1465
+ ); // 0.01% slippage
1466
+
1398
1467
  logger.verbose(
1399
1468
  `${EkuboCLVault.name}: harvest => swap1Amount: ${swap1Amount}`
1400
1469
  );
1401
- const remainingAmount = postFeeAmount.minus(swap1Amount);
1470
+
1471
+ const remainingAmount = postFeeAmount.minus(swap1Amount).maximum(0);
1402
1472
  logger.verbose(
1403
1473
  `${EkuboCLVault.name}: harvest => remainingAmount: ${remainingAmount}`
1404
1474
  );
@@ -1438,7 +1508,11 @@ export class EkuboCLVault extends BaseStrategy<
1438
1508
  const _callsFinal = await this.rebalanceIter(
1439
1509
  swapInfo,
1440
1510
  acc,
1441
- harvestEstimateCall
1511
+ harvestEstimateCall,
1512
+ claim.token.eq(poolKey.token0),
1513
+ 0,
1514
+ 0n,
1515
+ BigInt(postFeeAmount.toWei()), // upper limit is the post fee amount
1442
1516
  );
1443
1517
  logger.verbose(
1444
1518
  `${EkuboCLVault.name}: harvest => _callsFinal: ${JSON.stringify(