@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
|
@@ -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 =
|
|
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
|
-
//
|
|
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,
|
|
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
|
-
|
|
1095
|
-
Number(newRatio.toString()) < expectedRatio * (1 - 1 / 10 ** expectedPrecision)
|
|
1147
|
+
!isWithInTolerance
|
|
1096
1148
|
) {
|
|
1097
|
-
|
|
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: ${
|
|
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
|
-
|
|
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(
|