@strkfarm/sdk 1.0.28 → 1.0.29
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/cli.js +48 -24
- package/dist/cli.mjs +46 -22
- package/dist/index.browser.global.js +496 -75
- package/dist/index.browser.mjs +504 -82
- package/dist/index.d.ts +56 -15
- package/dist/index.js +511 -88
- package/dist/index.mjs +506 -84
- package/package.json +1 -1
- package/src/dataTypes/_bignumber.ts +15 -7
- package/src/global.ts +13 -6
- package/src/interfaces/common.ts +3 -0
- package/src/modules/avnu.ts +74 -59
- package/src/modules/harvests.ts +74 -0
- package/src/modules/pricer-from-api.ts +9 -8
- package/src/modules/zkLend.ts +2 -1
- package/src/strategies/base-strategy.ts +3 -3
- package/src/strategies/ekubo-cl-vault.ts +477 -55
- package/src/strategies/index.ts +2 -1
- package/src/strategies/vesu-rebalance.ts +2 -2
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ var src_exports = {};
|
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
AutoCompounderSTRK: () => AutoCompounderSTRK,
|
|
34
34
|
AvnuWrapper: () => AvnuWrapper,
|
|
35
|
+
BaseStrategy: () => BaseStrategy,
|
|
35
36
|
ContractAddr: () => ContractAddr,
|
|
36
37
|
ERC20: () => ERC20,
|
|
37
38
|
EkuboCLVault: () => EkuboCLVault,
|
|
@@ -86,19 +87,19 @@ var _Web3Number = class extends import_bignumber.default {
|
|
|
86
87
|
return this.mul(10 ** this.decimals).toFixed(0);
|
|
87
88
|
}
|
|
88
89
|
multipliedBy(value) {
|
|
89
|
-
|
|
90
|
+
const _value = this.getStandardString(value);
|
|
90
91
|
return this.construct(this.mul(_value).toString(), this.decimals);
|
|
91
92
|
}
|
|
92
93
|
dividedBy(value) {
|
|
93
|
-
|
|
94
|
+
const _value = this.getStandardString(value);
|
|
94
95
|
return this.construct(this.div(_value).toString(), this.decimals);
|
|
95
96
|
}
|
|
96
97
|
plus(value) {
|
|
97
|
-
const _value =
|
|
98
|
+
const _value = this.getStandardString(value);
|
|
98
99
|
return this.construct(this.add(_value).toString(), this.decimals);
|
|
99
100
|
}
|
|
100
101
|
minus(n, base) {
|
|
101
|
-
const _value =
|
|
102
|
+
const _value = this.getStandardString(n);
|
|
102
103
|
return this.construct(super.minus(_value, base).toString(), this.decimals);
|
|
103
104
|
}
|
|
104
105
|
construct(value, decimals) {
|
|
@@ -114,11 +115,17 @@ var _Web3Number = class extends import_bignumber.default {
|
|
|
114
115
|
return this.toString();
|
|
115
116
|
}
|
|
116
117
|
maxToFixedDecimals() {
|
|
117
|
-
return Math.min(this.decimals,
|
|
118
|
+
return Math.min(this.decimals, 18);
|
|
119
|
+
}
|
|
120
|
+
getStandardString(value) {
|
|
121
|
+
if (typeof value == "string") {
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
return value.toFixed(this.maxToFixedDecimals());
|
|
118
125
|
}
|
|
119
126
|
};
|
|
120
|
-
import_bignumber.default.config({ DECIMAL_PLACES: 18 });
|
|
121
|
-
_Web3Number.config({ DECIMAL_PLACES: 18 });
|
|
127
|
+
import_bignumber.default.config({ DECIMAL_PLACES: 18, ROUNDING_MODE: import_bignumber.default.ROUND_DOWN });
|
|
128
|
+
_Web3Number.config({ DECIMAL_PLACES: 18, ROUNDING_MODE: import_bignumber.default.ROUND_DOWN });
|
|
122
129
|
|
|
123
130
|
// src/dataTypes/bignumber.node.ts
|
|
124
131
|
var Web3Number = class _Web3Number2 extends _Web3Number {
|
|
@@ -190,42 +197,48 @@ var defaultTokens = [{
|
|
|
190
197
|
logo: "https://assets.coingecko.com/coins/images/26433/small/starknet.png",
|
|
191
198
|
address: ContractAddr.from("0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d"),
|
|
192
199
|
decimals: 18,
|
|
193
|
-
coingeckId: "starknet"
|
|
200
|
+
coingeckId: "starknet",
|
|
201
|
+
displayDecimals: 2
|
|
194
202
|
}, {
|
|
195
203
|
name: "xSTRK",
|
|
196
204
|
symbol: "xSTRK",
|
|
197
205
|
logo: "https://dashboard.endur.fi/endur-fi.svg",
|
|
198
206
|
address: ContractAddr.from("0x028d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a"),
|
|
199
207
|
decimals: 18,
|
|
200
|
-
coingeckId: void 0
|
|
208
|
+
coingeckId: void 0,
|
|
209
|
+
displayDecimals: 2
|
|
201
210
|
}, {
|
|
202
211
|
name: "ETH",
|
|
203
212
|
symbol: "ETH",
|
|
204
213
|
logo: "https://opbnb.bscscan.com/token/images/ether.svg",
|
|
205
214
|
address: ContractAddr.from("0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"),
|
|
206
215
|
decimals: 18,
|
|
207
|
-
coingeckId: void 0
|
|
216
|
+
coingeckId: void 0,
|
|
217
|
+
displayDecimals: 4
|
|
208
218
|
}, {
|
|
209
219
|
name: "USDC",
|
|
210
220
|
symbol: "USDC",
|
|
211
221
|
logo: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
|
|
212
222
|
address: ContractAddr.from("0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"),
|
|
213
223
|
decimals: 6,
|
|
214
|
-
coingeckId: void 0
|
|
224
|
+
coingeckId: void 0,
|
|
225
|
+
displayDecimals: 2
|
|
215
226
|
}, {
|
|
216
227
|
name: "USDT",
|
|
217
228
|
symbol: "USDT",
|
|
218
229
|
logo: "https://assets.coingecko.com/coins/images/325/small/Tether.png",
|
|
219
230
|
address: ContractAddr.from("0x68f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"),
|
|
220
231
|
decimals: 6,
|
|
221
|
-
coingeckId: void 0
|
|
232
|
+
coingeckId: void 0,
|
|
233
|
+
displayDecimals: 2
|
|
222
234
|
}, {
|
|
223
235
|
name: "WBTC",
|
|
224
236
|
symbol: "WBTC",
|
|
225
237
|
logo: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599/logo.png",
|
|
226
238
|
address: ContractAddr.from("0x3fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac"),
|
|
227
239
|
decimals: 8,
|
|
228
|
-
coingeckId: void 0
|
|
240
|
+
coingeckId: void 0,
|
|
241
|
+
displayDecimals: 6
|
|
229
242
|
}];
|
|
230
243
|
var tokens = defaultTokens;
|
|
231
244
|
var Global = class _Global {
|
|
@@ -257,7 +270,8 @@ var Global = class _Global {
|
|
|
257
270
|
address: ContractAddr.from(token.address),
|
|
258
271
|
decimals: token.decimals,
|
|
259
272
|
logo: token.logoUri,
|
|
260
|
-
coingeckId: token.extensions.coingeckoId
|
|
273
|
+
coingeckId: token.extensions.coingeckoId,
|
|
274
|
+
displayDecimals: 2
|
|
261
275
|
});
|
|
262
276
|
});
|
|
263
277
|
console.log(tokens);
|
|
@@ -641,7 +655,8 @@ var _ZkLend = class _ZkLend extends ILending {
|
|
|
641
655
|
logo: "",
|
|
642
656
|
decimals: pool.token.decimals,
|
|
643
657
|
borrowFactor: Web3Number2.fromWei(pool.borrow_factor.value, pool.borrow_factor.decimals),
|
|
644
|
-
collareralFactor
|
|
658
|
+
collareralFactor,
|
|
659
|
+
displayDecimals: 2
|
|
645
660
|
};
|
|
646
661
|
this.tokens.push(token);
|
|
647
662
|
});
|
|
@@ -758,7 +773,7 @@ var PricerFromApi = class extends PricerBase {
|
|
|
758
773
|
try {
|
|
759
774
|
return await this.getPriceFromMyAPI(tokenSymbol);
|
|
760
775
|
} catch (e) {
|
|
761
|
-
logger.warn("getPriceFromMyAPI error", e);
|
|
776
|
+
logger.warn("getPriceFromMyAPI error", JSON.stringify(e.message || e));
|
|
762
777
|
}
|
|
763
778
|
logger.log("getPrice coinbase", tokenSymbol);
|
|
764
779
|
let retry = 0;
|
|
@@ -778,7 +793,7 @@ var PricerFromApi = class extends PricerBase {
|
|
|
778
793
|
timestamp: /* @__PURE__ */ new Date()
|
|
779
794
|
};
|
|
780
795
|
} catch (e) {
|
|
781
|
-
logger.warn("getPrice coinbase error", e
|
|
796
|
+
logger.warn("getPrice coinbase error", JSON.stringify(e.message || e));
|
|
782
797
|
await new Promise((resolve) => setTimeout(resolve, retry * 1e3));
|
|
783
798
|
}
|
|
784
799
|
}
|
|
@@ -792,9 +807,6 @@ var PricerFromApi = class extends PricerBase {
|
|
|
792
807
|
const priceInfo = await priceInfoRes.json();
|
|
793
808
|
const now = /* @__PURE__ */ new Date();
|
|
794
809
|
const priceTime = new Date(priceInfo.timestamp);
|
|
795
|
-
if (now.getTime() - priceTime.getTime() > 9e5) {
|
|
796
|
-
throw new Error("Price is stale");
|
|
797
|
-
}
|
|
798
810
|
const price = Number(priceInfo.price);
|
|
799
811
|
return {
|
|
800
812
|
price,
|
|
@@ -1958,17 +1970,28 @@ function assert(condition, message) {
|
|
|
1958
1970
|
}
|
|
1959
1971
|
|
|
1960
1972
|
// src/modules/avnu.ts
|
|
1961
|
-
var AvnuWrapper = class {
|
|
1962
|
-
async getQuotes(fromToken, toToken, amountWei, taker) {
|
|
1973
|
+
var AvnuWrapper = class _AvnuWrapper {
|
|
1974
|
+
async getQuotes(fromToken, toToken, amountWei, taker, retry = 0) {
|
|
1975
|
+
const MAX_RETRY = 5;
|
|
1976
|
+
logger.verbose(`${_AvnuWrapper.name}: getQuotes => Getting quotes for ${fromToken} -> ${toToken}, amount: ${amountWei}, taker: ${taker}, retry: ${retry}`);
|
|
1963
1977
|
const params = {
|
|
1964
1978
|
sellTokenAddress: fromToken,
|
|
1965
1979
|
buyTokenAddress: toToken,
|
|
1966
1980
|
sellAmount: amountWei,
|
|
1967
|
-
takerAddress: taker
|
|
1981
|
+
takerAddress: taker,
|
|
1982
|
+
// excludeSources: ['Nostra', 'Haiko(Solvers)']
|
|
1983
|
+
excludeSources: ["Haiko(Solvers)"]
|
|
1984
|
+
// to resolve InvalidOraclePrice error
|
|
1968
1985
|
};
|
|
1969
1986
|
assert(fromToken != toToken, "From and to tokens are the same");
|
|
1970
1987
|
const quotes = await (0, import_avnu_sdk.fetchQuotes)(params);
|
|
1971
|
-
|
|
1988
|
+
if (quotes.length == 0) {
|
|
1989
|
+
if (retry < MAX_RETRY) {
|
|
1990
|
+
await new Promise((res) => setTimeout(res, 3e3));
|
|
1991
|
+
return await this.getQuotes(fromToken, toToken, amountWei, taker, retry + 1);
|
|
1992
|
+
}
|
|
1993
|
+
throw new Error("no quotes found");
|
|
1994
|
+
}
|
|
1972
1995
|
return quotes[0];
|
|
1973
1996
|
}
|
|
1974
1997
|
async getSwapInfo(quote, taker, integratorFeeBps, integratorFeeRecipient, minAmount) {
|
|
@@ -3641,10 +3664,10 @@ var BaseStrategy = class {
|
|
|
3641
3664
|
async getTVL() {
|
|
3642
3665
|
throw new Error("Not implemented");
|
|
3643
3666
|
}
|
|
3644
|
-
depositCall(amountInfo, receiver) {
|
|
3667
|
+
async depositCall(amountInfo, receiver) {
|
|
3645
3668
|
throw new Error("Not implemented");
|
|
3646
3669
|
}
|
|
3647
|
-
withdrawCall(amountInfo, receiver, owner) {
|
|
3670
|
+
async withdrawCall(amountInfo, receiver, owner) {
|
|
3648
3671
|
throw new Error("Not implemented");
|
|
3649
3672
|
}
|
|
3650
3673
|
};
|
|
@@ -3681,7 +3704,7 @@ var VesuRebalance = class _VesuRebalance extends BaseStrategy {
|
|
|
3681
3704
|
* @param receiver - Address that will receive the strategy tokens
|
|
3682
3705
|
* @returns Populated contract call for deposit
|
|
3683
3706
|
*/
|
|
3684
|
-
depositCall(amountInfo, receiver) {
|
|
3707
|
+
async depositCall(amountInfo, receiver) {
|
|
3685
3708
|
assert(amountInfo.tokenInfo.address.eq(this.asset().address), "Deposit token mismatch");
|
|
3686
3709
|
const assetContract = new import_starknet7.Contract(vesu_rebalance_abi_default, this.asset().address.address, this.config.provider);
|
|
3687
3710
|
const call1 = assetContract.populate("approve", [this.address.address, import_starknet7.uint256.bnToUint256(amountInfo.amount.toWei())]);
|
|
@@ -3695,7 +3718,7 @@ var VesuRebalance = class _VesuRebalance extends BaseStrategy {
|
|
|
3695
3718
|
* @param owner - Address that owns the strategy tokens
|
|
3696
3719
|
* @returns Populated contract call for withdrawal
|
|
3697
3720
|
*/
|
|
3698
|
-
withdrawCall(amountInfo, receiver, owner) {
|
|
3721
|
+
async withdrawCall(amountInfo, receiver, owner) {
|
|
3699
3722
|
return [this.contract.populate("withdraw", [import_starknet7.uint256.bnToUint256(amountInfo.amount.toWei()), receiver.address, owner.address])];
|
|
3700
3723
|
}
|
|
3701
3724
|
/**
|
|
@@ -4098,7 +4121,7 @@ var VesuRebalanceStrategies = [{
|
|
|
4098
4121
|
}];
|
|
4099
4122
|
|
|
4100
4123
|
// src/strategies/ekubo-cl-vault.ts
|
|
4101
|
-
var
|
|
4124
|
+
var import_starknet9 = require("starknet");
|
|
4102
4125
|
|
|
4103
4126
|
// src/data/cl-vault.abi.json
|
|
4104
4127
|
var cl_vault_abi_default = [
|
|
@@ -8999,6 +9022,58 @@ var erc4626_abi_default = [
|
|
|
8999
9022
|
}
|
|
9000
9023
|
];
|
|
9001
9024
|
|
|
9025
|
+
// src/modules/harvests.ts
|
|
9026
|
+
var import_starknet8 = require("starknet");
|
|
9027
|
+
var Harvests = class _Harvests {
|
|
9028
|
+
constructor(config) {
|
|
9029
|
+
this.config = config;
|
|
9030
|
+
}
|
|
9031
|
+
getHarvests(addr) {
|
|
9032
|
+
throw new Error("Not implemented");
|
|
9033
|
+
}
|
|
9034
|
+
async getUnHarvestedRewards(addr) {
|
|
9035
|
+
const rewards = await this.getHarvests(addr);
|
|
9036
|
+
if (rewards.length == 0) return [];
|
|
9037
|
+
const unClaimed = [];
|
|
9038
|
+
const cls = await this.config.provider.getClassAt(rewards[0].rewardsContract.address);
|
|
9039
|
+
for (let reward of rewards) {
|
|
9040
|
+
const contract = new import_starknet8.Contract(cls.abi, reward.rewardsContract.address, this.config.provider);
|
|
9041
|
+
const isClaimed = await contract.call("is_claimed", [reward.claim.id]);
|
|
9042
|
+
logger.verbose(`${_Harvests.name}: isClaimed: ${isClaimed}`);
|
|
9043
|
+
if (isClaimed)
|
|
9044
|
+
return unClaimed;
|
|
9045
|
+
unClaimed.unshift(reward);
|
|
9046
|
+
}
|
|
9047
|
+
return unClaimed;
|
|
9048
|
+
}
|
|
9049
|
+
};
|
|
9050
|
+
var EkuboHarvests = class extends Harvests {
|
|
9051
|
+
async getHarvests(addr) {
|
|
9052
|
+
const STRK = "0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
|
|
9053
|
+
const EKUBO_API = `https://starknet-mainnet-api.ekubo.org/airdrops/${addr.address}?token=${STRK}`;
|
|
9054
|
+
const resultEkubo = await fetch(EKUBO_API);
|
|
9055
|
+
const items = await resultEkubo.json();
|
|
9056
|
+
const rewards = [];
|
|
9057
|
+
for (let i = 0; i < items.length; ++i) {
|
|
9058
|
+
const info = items[i];
|
|
9059
|
+
assert(info.token == STRK, "expected strk token only");
|
|
9060
|
+
rewards.push({
|
|
9061
|
+
rewardsContract: ContractAddr.from(info.contract_address),
|
|
9062
|
+
token: ContractAddr.from(STRK),
|
|
9063
|
+
startDate: new Date(info.start_date),
|
|
9064
|
+
endDate: new Date(info.end_date),
|
|
9065
|
+
claim: {
|
|
9066
|
+
id: info.claim.id,
|
|
9067
|
+
amount: Web3Number.fromWei(info.claim.amount, 18),
|
|
9068
|
+
claimee: ContractAddr.from(info.claim.claimee)
|
|
9069
|
+
},
|
|
9070
|
+
proof: info.proof
|
|
9071
|
+
});
|
|
9072
|
+
}
|
|
9073
|
+
return rewards.sort((a, b) => b.endDate.getTime() - a.endDate.getTime());
|
|
9074
|
+
}
|
|
9075
|
+
};
|
|
9076
|
+
|
|
9002
9077
|
// src/strategies/ekubo-cl-vault.ts
|
|
9003
9078
|
var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
9004
9079
|
/**
|
|
@@ -9015,19 +9090,74 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9015
9090
|
assert(metadata.depositTokens.length === 2, "EkuboCL only supports 2 deposit token");
|
|
9016
9091
|
this.metadata = metadata;
|
|
9017
9092
|
this.address = metadata.address;
|
|
9018
|
-
this.contract = new
|
|
9019
|
-
this.lstContract = new
|
|
9093
|
+
this.contract = new import_starknet9.Contract(cl_vault_abi_default, this.address.address, this.config.provider);
|
|
9094
|
+
this.lstContract = new import_starknet9.Contract(erc4626_abi_default, this.metadata.additionalInfo.lstContract.address, this.config.provider);
|
|
9020
9095
|
const EKUBO_POSITION = "0x02e0af29598b407c8716b17f6d2795eca1b471413fa03fb145a5e33722184067";
|
|
9021
|
-
this.ekuboPositionsContract = new
|
|
9096
|
+
this.ekuboPositionsContract = new import_starknet9.Contract(ekubo_positions_abi_default, EKUBO_POSITION, this.config.provider);
|
|
9022
9097
|
const EKUBO_MATH = "0x04a72e9e166f6c0e9d800af4dc40f6b6fb4404b735d3f528d9250808b2481995";
|
|
9023
|
-
this.ekuboMathContract = new
|
|
9098
|
+
this.ekuboMathContract = new import_starknet9.Contract(ekubo_math_abi_default, EKUBO_MATH, this.config.provider);
|
|
9024
9099
|
this.avnu = new AvnuWrapper();
|
|
9025
9100
|
}
|
|
9026
|
-
|
|
9027
|
-
|
|
9101
|
+
async matchInputAmounts(amountInfo) {
|
|
9102
|
+
const bounds = await this.getCurrentBounds();
|
|
9103
|
+
const res = await this._getExpectedAmountsForLiquidity(
|
|
9104
|
+
amountInfo.token0.amount,
|
|
9105
|
+
amountInfo.token1.amount,
|
|
9106
|
+
bounds,
|
|
9107
|
+
false
|
|
9108
|
+
);
|
|
9109
|
+
return {
|
|
9110
|
+
token0: {
|
|
9111
|
+
tokenInfo: amountInfo.token0.tokenInfo,
|
|
9112
|
+
amount: res.amount0
|
|
9113
|
+
},
|
|
9114
|
+
token1: {
|
|
9115
|
+
tokenInfo: amountInfo.token1.tokenInfo,
|
|
9116
|
+
amount: res.amount1
|
|
9117
|
+
}
|
|
9118
|
+
};
|
|
9119
|
+
}
|
|
9120
|
+
/** Returns minimum amounts give given two amounts based on what can be added for liq */
|
|
9121
|
+
async getMinDepositAmounts(amountInfo) {
|
|
9122
|
+
const shares = await this.tokensToShares(amountInfo);
|
|
9123
|
+
const { amount0, amount1 } = await this.contract.call("convert_to_assets", [import_starknet9.uint256.bnToUint256(shares.toWei())]);
|
|
9124
|
+
return {
|
|
9125
|
+
token0: {
|
|
9126
|
+
tokenInfo: amountInfo.token0.tokenInfo,
|
|
9127
|
+
amount: Web3Number.fromWei(amount0.toString(), amountInfo.token0.tokenInfo.decimals)
|
|
9128
|
+
},
|
|
9129
|
+
token1: {
|
|
9130
|
+
tokenInfo: amountInfo.token1.tokenInfo,
|
|
9131
|
+
amount: Web3Number.fromWei(amount1.toString(), amountInfo.token1.tokenInfo.decimals)
|
|
9132
|
+
}
|
|
9133
|
+
};
|
|
9028
9134
|
}
|
|
9029
|
-
|
|
9030
|
-
|
|
9135
|
+
async depositCall(amountInfo, receiver) {
|
|
9136
|
+
const updateAmountInfo = await this.getMinDepositAmounts(amountInfo);
|
|
9137
|
+
const token0Contract = new import_starknet9.Contract(erc4626_abi_default, amountInfo.token0.tokenInfo.address.address, this.config.provider);
|
|
9138
|
+
const token1Contract = new import_starknet9.Contract(erc4626_abi_default, amountInfo.token1.tokenInfo.address.address, this.config.provider);
|
|
9139
|
+
const call1 = token0Contract.populate("approve", [this.address.address, import_starknet9.uint256.bnToUint256(updateAmountInfo.token0.amount.toWei())]);
|
|
9140
|
+
const call2 = token1Contract.populate("approve", [this.address.address, import_starknet9.uint256.bnToUint256(updateAmountInfo.token1.amount.toWei())]);
|
|
9141
|
+
const call3 = this.contract.populate("deposit", [import_starknet9.uint256.bnToUint256(updateAmountInfo.token0.amount.toWei()), import_starknet9.uint256.bnToUint256(updateAmountInfo.token1.amount.toWei()), receiver.address]);
|
|
9142
|
+
const calls = [];
|
|
9143
|
+
if (updateAmountInfo.token0.amount.greaterThan(0)) calls.push(call1);
|
|
9144
|
+
if (updateAmountInfo.token1.amount.greaterThan(0)) calls.push(call2);
|
|
9145
|
+
return [...calls, call3];
|
|
9146
|
+
}
|
|
9147
|
+
async tokensToShares(amountInfo) {
|
|
9148
|
+
const shares = await this.contract.call("convert_to_shares", [
|
|
9149
|
+
import_starknet9.uint256.bnToUint256(amountInfo.token0.amount.toWei()),
|
|
9150
|
+
import_starknet9.uint256.bnToUint256(amountInfo.token1.amount.toWei())
|
|
9151
|
+
]);
|
|
9152
|
+
return Web3Number.fromWei(shares.toString(), 18);
|
|
9153
|
+
}
|
|
9154
|
+
async withdrawCall(amountInfo, receiver, owner) {
|
|
9155
|
+
const shares = await this.tokensToShares(amountInfo);
|
|
9156
|
+
logger.verbose(`${_EkuboCLVault.name}: withdrawCall: shares=${shares.toString()}`);
|
|
9157
|
+
return [this.contract.populate("withdraw", [
|
|
9158
|
+
import_starknet9.uint256.bnToUint256(shares.toWei()),
|
|
9159
|
+
receiver.address
|
|
9160
|
+
])];
|
|
9031
9161
|
}
|
|
9032
9162
|
rebalanceCall(newBounds, swapParams) {
|
|
9033
9163
|
return [this.contract.populate("rebalance", [
|
|
@@ -9046,22 +9176,121 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9046
9176
|
handleFeesCall() {
|
|
9047
9177
|
return [this.contract.populate("handle_fees", [])];
|
|
9048
9178
|
}
|
|
9049
|
-
|
|
9050
|
-
|
|
9179
|
+
/**
|
|
9180
|
+
* Calculates assets before and now in a given token of TVL per share to observe growth
|
|
9181
|
+
* @returns {Promise<number>} The weighted average APY across all pools
|
|
9182
|
+
*/
|
|
9183
|
+
async netAPY(blockIdentifier = "pending", sinceBlocks = 2e4) {
|
|
9184
|
+
const tvlNow = await this._getTVL(blockIdentifier);
|
|
9185
|
+
const supplyNow = await this.totalSupply(blockIdentifier);
|
|
9186
|
+
const priceNow = await this.getCurrentPrice(blockIdentifier);
|
|
9187
|
+
let blockNow = typeof blockIdentifier == "number" ? blockIdentifier : (await this.config.provider.getBlockLatestAccepted()).block_number;
|
|
9188
|
+
const blockNowTime = typeof blockIdentifier == "number" ? (await this.config.provider.getBlockWithTxs(blockIdentifier)).timestamp : (/* @__PURE__ */ new Date()).getTime() / 1e3;
|
|
9189
|
+
const blockBefore = blockNow - sinceBlocks;
|
|
9190
|
+
const adjustedSupplyNow = supplyNow.minus(await this.getHarvestRewardShares(blockBefore, blockNow));
|
|
9191
|
+
let blockBeforeInfo = await this.config.provider.getBlockWithTxs(blockBefore);
|
|
9192
|
+
const tvlBefore = await this._getTVL(blockBefore);
|
|
9193
|
+
const supplyBefore = await this.totalSupply(blockBefore);
|
|
9194
|
+
const priceBefore = await this.getCurrentPrice(blockBefore);
|
|
9195
|
+
const tvlInToken0Now = tvlNow.amount0.multipliedBy(priceNow.price).plus(tvlNow.amount1);
|
|
9196
|
+
const tvlPerShareNow = tvlInToken0Now.multipliedBy(1e18).dividedBy(adjustedSupplyNow);
|
|
9197
|
+
const tvlInToken0Bf = tvlBefore.amount0.multipliedBy(priceBefore.price).plus(tvlBefore.amount1);
|
|
9198
|
+
const tvlPerShareBf = tvlInToken0Bf.multipliedBy(1e18).dividedBy(supplyBefore);
|
|
9199
|
+
const timeDiffSeconds = blockNowTime - blockBeforeInfo.timestamp;
|
|
9200
|
+
logger.verbose(`tvlInToken0Now: ${tvlInToken0Now.toString()}`);
|
|
9201
|
+
logger.verbose(`tvlInToken0Bf: ${tvlInToken0Bf.toString()}`);
|
|
9202
|
+
logger.verbose(`tvlPerShareNow: ${tvlPerShareNow.toString()}`);
|
|
9203
|
+
logger.verbose(`tvlPerShareBf: ${tvlPerShareBf.toString()}`);
|
|
9204
|
+
logger.verbose(`Price before: ${priceBefore.price.toString()}`);
|
|
9205
|
+
logger.verbose(`Price now: ${priceNow.price.toString()}`);
|
|
9206
|
+
logger.verbose(`Supply before: ${supplyBefore.toString()}`);
|
|
9207
|
+
logger.verbose(`Supply now: ${adjustedSupplyNow.toString()}`);
|
|
9208
|
+
logger.verbose(`Time diff in seconds: ${timeDiffSeconds}`);
|
|
9209
|
+
const apyForGivenBlocks = Number(tvlPerShareNow.minus(tvlPerShareBf).multipliedBy(1e4).dividedBy(tvlPerShareBf)) / 1e4;
|
|
9210
|
+
return apyForGivenBlocks * (365 * 24 * 3600) / timeDiffSeconds;
|
|
9211
|
+
}
|
|
9212
|
+
async getHarvestRewardShares(fromBlock, toBlock) {
|
|
9213
|
+
const len = Number(await this.contract.call("get_total_rewards"));
|
|
9214
|
+
let shares = Web3Number.fromWei(0, 18);
|
|
9215
|
+
for (let i = len - 1; i > 0; --i) {
|
|
9216
|
+
let record = await this.contract.call("get_rewards_info", [i]);
|
|
9217
|
+
logger.verbose(`${_EkuboCLVault.name}: getHarvestRewardShares: ${i}`);
|
|
9218
|
+
console.log(record);
|
|
9219
|
+
const block = Number(record.block_number);
|
|
9220
|
+
if (block < fromBlock) {
|
|
9221
|
+
return shares;
|
|
9222
|
+
} else if (block > toBlock) {
|
|
9223
|
+
continue;
|
|
9224
|
+
} else {
|
|
9225
|
+
shares = shares.plus(Web3Number.fromWei(record.shares.toString(), 18));
|
|
9226
|
+
}
|
|
9227
|
+
logger.verbose(`${_EkuboCLVault.name}: getHarvestRewardShares: ${i} => ${shares.toWei()}`);
|
|
9228
|
+
}
|
|
9229
|
+
return shares;
|
|
9051
9230
|
}
|
|
9052
|
-
async
|
|
9053
|
-
|
|
9054
|
-
|
|
9055
|
-
|
|
9056
|
-
|
|
9231
|
+
async balanceOf(user, blockIdentifier = "pending") {
|
|
9232
|
+
let bal = await this.contract.call("balance_of", [user.address]);
|
|
9233
|
+
return Web3Number.fromWei(bal.toString(), 18);
|
|
9234
|
+
}
|
|
9235
|
+
async getUserTVL(user, blockIdentifier = "pending") {
|
|
9236
|
+
let bal = await this.balanceOf(user, blockIdentifier);
|
|
9237
|
+
const assets = await this.contract.call("convert_to_assets", [import_starknet9.uint256.bnToUint256(bal.toWei())], {
|
|
9238
|
+
blockIdentifier
|
|
9239
|
+
});
|
|
9240
|
+
const poolKey = await this.getPoolKey(blockIdentifier);
|
|
9241
|
+
this.assertValidDepositTokens(poolKey);
|
|
9057
9242
|
const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
|
|
9058
9243
|
const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
|
|
9244
|
+
const amount0 = Web3Number.fromWei(assets.amount0.toString(), token0Info.decimals);
|
|
9245
|
+
const amount1 = Web3Number.fromWei(assets.amount1.toString(), token1Info.decimals);
|
|
9059
9246
|
const P0 = await this.pricer.getPrice(token0Info.symbol);
|
|
9060
9247
|
const P1 = await this.pricer.getPrice(token1Info.symbol);
|
|
9061
9248
|
const token0Usd = Number(amount0.toFixed(13)) * P0.price;
|
|
9062
9249
|
const token1Usd = Number(amount1.toFixed(13)) * P1.price;
|
|
9063
9250
|
return {
|
|
9064
|
-
|
|
9251
|
+
usdValue: token0Usd + token1Usd,
|
|
9252
|
+
token0: {
|
|
9253
|
+
tokenInfo: token0Info,
|
|
9254
|
+
amount: amount0,
|
|
9255
|
+
usdValue: token0Usd
|
|
9256
|
+
},
|
|
9257
|
+
token1: {
|
|
9258
|
+
tokenInfo: token1Info,
|
|
9259
|
+
amount: amount1,
|
|
9260
|
+
usdValue: token1Usd
|
|
9261
|
+
}
|
|
9262
|
+
};
|
|
9263
|
+
}
|
|
9264
|
+
async _getTVL(blockIdentifier = "pending") {
|
|
9265
|
+
const result = await this.contract.call("total_liquidity", [], {
|
|
9266
|
+
blockIdentifier
|
|
9267
|
+
});
|
|
9268
|
+
const bounds = await this.getCurrentBounds(blockIdentifier);
|
|
9269
|
+
const { amount0, amount1 } = await this.getLiquidityToAmounts(Web3Number.fromWei(result.toString(), 18), bounds, blockIdentifier);
|
|
9270
|
+
return { amount0, amount1 };
|
|
9271
|
+
}
|
|
9272
|
+
async totalSupply(blockIdentifier = "pending") {
|
|
9273
|
+
const res = await this.contract.call("total_supply", [], {
|
|
9274
|
+
blockIdentifier
|
|
9275
|
+
});
|
|
9276
|
+
return Web3Number.fromWei(res.toString(), 18);
|
|
9277
|
+
}
|
|
9278
|
+
assertValidDepositTokens(poolKey) {
|
|
9279
|
+
assert(poolKey.token0.eq(this.metadata.depositTokens[0].address), "Expected token0 in depositTokens[0]");
|
|
9280
|
+
assert(poolKey.token1.eq(this.metadata.depositTokens[1].address), "Expected token1 in depositTokens[1]");
|
|
9281
|
+
}
|
|
9282
|
+
async getTVL(blockIdentifier = "pending") {
|
|
9283
|
+
const { amount0, amount1 } = await this._getTVL(blockIdentifier);
|
|
9284
|
+
const poolKey = await this.getPoolKey(blockIdentifier);
|
|
9285
|
+
this.assertValidDepositTokens(poolKey);
|
|
9286
|
+
const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
|
|
9287
|
+
const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
|
|
9288
|
+
const P0 = await this.pricer.getPrice(token0Info.symbol);
|
|
9289
|
+
const P1 = await this.pricer.getPrice(token1Info.symbol);
|
|
9290
|
+
const token0Usd = Number(amount0.toFixed(13)) * P0.price;
|
|
9291
|
+
const token1Usd = Number(amount1.toFixed(13)) * P1.price;
|
|
9292
|
+
return {
|
|
9293
|
+
usdValue: token0Usd + token1Usd,
|
|
9065
9294
|
token0: {
|
|
9066
9295
|
tokenInfo: token0Info,
|
|
9067
9296
|
amount: amount0,
|
|
@@ -9101,7 +9330,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9101
9330
|
const token0Usd = Number(token0Web3.toFixed(13)) * P0.price;
|
|
9102
9331
|
const token1Usd = Number(token1Web3.toFixed(13)) * P1.price;
|
|
9103
9332
|
return {
|
|
9104
|
-
|
|
9333
|
+
usdValue: token0Usd + token1Usd,
|
|
9105
9334
|
token0: {
|
|
9106
9335
|
tokenInfo: token0Info,
|
|
9107
9336
|
amount: token0Web3,
|
|
@@ -9119,15 +9348,15 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9119
9348
|
return Number(result.salt.toString());
|
|
9120
9349
|
}
|
|
9121
9350
|
async truePrice() {
|
|
9122
|
-
const result = await this.lstContract.call("convert_to_assets", [
|
|
9351
|
+
const result = await this.lstContract.call("convert_to_assets", [import_starknet9.uint256.bnToUint256(BigInt(1e18).toString())]);
|
|
9123
9352
|
const truePrice = Number(BigInt(result.toString()) * BigInt(1e9) / BigInt(1e18)) / 1e9;
|
|
9124
9353
|
return truePrice;
|
|
9125
9354
|
}
|
|
9126
|
-
async getCurrentPrice() {
|
|
9127
|
-
const poolKey = await this.getPoolKey();
|
|
9128
|
-
return this._getCurrentPrice(poolKey);
|
|
9355
|
+
async getCurrentPrice(blockIdentifier = "pending") {
|
|
9356
|
+
const poolKey = await this.getPoolKey(blockIdentifier);
|
|
9357
|
+
return this._getCurrentPrice(poolKey, blockIdentifier);
|
|
9129
9358
|
}
|
|
9130
|
-
async _getCurrentPrice(poolKey) {
|
|
9359
|
+
async _getCurrentPrice(poolKey, blockIdentifier = "pending") {
|
|
9131
9360
|
const priceInfo = await this.ekuboPositionsContract.call("get_pool_price", [
|
|
9132
9361
|
{
|
|
9133
9362
|
token0: poolKey.token0.address,
|
|
@@ -9136,35 +9365,44 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9136
9365
|
tick_spacing: poolKey.tick_spacing,
|
|
9137
9366
|
extension: poolKey.extension
|
|
9138
9367
|
}
|
|
9139
|
-
]
|
|
9368
|
+
], {
|
|
9369
|
+
blockIdentifier
|
|
9370
|
+
});
|
|
9140
9371
|
const sqrtRatio = _EkuboCLVault.div2Power128(BigInt(priceInfo.sqrt_ratio.toString()));
|
|
9372
|
+
console.log(`EkuboCLVault: getCurrentPrice: blockIdentifier: ${blockIdentifier}, sqrtRatio: ${sqrtRatio}, ${priceInfo.sqrt_ratio.toString()}`);
|
|
9141
9373
|
const price = sqrtRatio * sqrtRatio;
|
|
9142
9374
|
const tick = _EkuboCLVault.priceToTick(price, true, Number(poolKey.tick_spacing));
|
|
9375
|
+
console.log(`EkuboCLVault: getCurrentPrice: blockIdentifier: ${blockIdentifier}, price: ${price}, tick: ${tick.mag}, ${tick.sign}`);
|
|
9143
9376
|
return {
|
|
9144
9377
|
price,
|
|
9145
|
-
tick: tick.mag * (tick.sign == 0 ? 1 : -1)
|
|
9378
|
+
tick: tick.mag * (tick.sign == 0 ? 1 : -1),
|
|
9379
|
+
sqrtRatio: priceInfo.sqrt_ratio.toString()
|
|
9146
9380
|
};
|
|
9147
9381
|
}
|
|
9148
|
-
async getCurrentBounds() {
|
|
9149
|
-
const result = await this.contract.call("get_position_key", []
|
|
9382
|
+
async getCurrentBounds(blockIdentifier = "pending") {
|
|
9383
|
+
const result = await this.contract.call("get_position_key", [], {
|
|
9384
|
+
blockIdentifier
|
|
9385
|
+
});
|
|
9150
9386
|
return {
|
|
9151
9387
|
lowerTick: _EkuboCLVault.i129ToNumber(result.bounds.lower),
|
|
9152
9388
|
upperTick: _EkuboCLVault.i129ToNumber(result.bounds.upper)
|
|
9153
9389
|
};
|
|
9154
9390
|
}
|
|
9155
|
-
static div2Power128(
|
|
9156
|
-
return Number(BigInt(
|
|
9391
|
+
static div2Power128(num4) {
|
|
9392
|
+
return Number(BigInt(num4.toString()) * 1000000n / BigInt(2 ** 128)) / 1e6;
|
|
9157
9393
|
}
|
|
9158
9394
|
static priceToTick(price, isRoundDown, tickSpacing) {
|
|
9159
9395
|
const value = isRoundDown ? Math.floor(Math.log(price) / Math.log(1.000001)) : Math.ceil(Math.log(price) / Math.log(1.000001));
|
|
9160
9396
|
const tick = Math.floor(value / tickSpacing) * tickSpacing;
|
|
9161
9397
|
return this.tickToi129(tick);
|
|
9162
9398
|
}
|
|
9163
|
-
async getPoolKey() {
|
|
9399
|
+
async getPoolKey(blockIdentifier = "pending") {
|
|
9164
9400
|
if (this.poolKey) {
|
|
9165
9401
|
return this.poolKey;
|
|
9166
9402
|
}
|
|
9167
|
-
const result = await this.contract.call("get_settings", []
|
|
9403
|
+
const result = await this.contract.call("get_settings", [], {
|
|
9404
|
+
blockIdentifier
|
|
9405
|
+
});
|
|
9168
9406
|
const poolKey = {
|
|
9169
9407
|
token0: ContractAddr.from(result.pool_key.token0.toString()),
|
|
9170
9408
|
token1: ContractAddr.from(result.pool_key.token1.toString()),
|
|
@@ -9195,25 +9433,24 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9195
9433
|
* @param amount1: amount of token1
|
|
9196
9434
|
* @returns {amount0, amount1}
|
|
9197
9435
|
*/
|
|
9198
|
-
async _getExpectedAmountsForLiquidity(amount0, amount1, bounds) {
|
|
9436
|
+
async _getExpectedAmountsForLiquidity(amount0, amount1, bounds, justUseInputAmount = true) {
|
|
9199
9437
|
assert(amount0.greaterThan(0) || amount1.greaterThan(0), "Amount is 0");
|
|
9200
|
-
const
|
|
9201
|
-
const sampleLiq = 1e18;
|
|
9438
|
+
const sampleLiq = 1e20;
|
|
9202
9439
|
const { amount0: sampleAmount0, amount1: sampleAmount1 } = await this.getLiquidityToAmounts(Web3Number.fromWei(sampleLiq.toString(), 18), bounds);
|
|
9203
9440
|
logger.verbose(`${_EkuboCLVault.name}: _getExpectedAmountsForLiquidity => sampleAmount0: ${sampleAmount0.toString()}, sampleAmount1: ${sampleAmount1.toString()}`);
|
|
9204
|
-
assert(!sampleAmount0.eq(0)
|
|
9441
|
+
assert(!sampleAmount0.eq(0) || !sampleAmount1.eq(0), "Sample amount is 0");
|
|
9205
9442
|
const price = await (await this.getCurrentPrice()).price;
|
|
9206
9443
|
logger.verbose(`${_EkuboCLVault.name}: _getExpectedAmountsForLiquidity => price: ${price}`);
|
|
9207
9444
|
if (amount1.eq(0) && amount0.greaterThan(0)) {
|
|
9208
9445
|
if (sampleAmount1.eq(0)) {
|
|
9209
9446
|
return {
|
|
9210
9447
|
amount0,
|
|
9211
|
-
amount1: Web3Number.fromWei("0",
|
|
9448
|
+
amount1: Web3Number.fromWei("0", amount1.decimals),
|
|
9212
9449
|
ratio: Infinity
|
|
9213
9450
|
};
|
|
9214
9451
|
} else if (sampleAmount0.eq(0)) {
|
|
9215
9452
|
return {
|
|
9216
|
-
amount0: Web3Number.fromWei("0",
|
|
9453
|
+
amount0: Web3Number.fromWei("0", amount0.decimals),
|
|
9217
9454
|
amount1: amount0.multipliedBy(price),
|
|
9218
9455
|
ratio: 0
|
|
9219
9456
|
};
|
|
@@ -9221,21 +9458,41 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9221
9458
|
} else if (amount0.eq(0) && amount1.greaterThan(0)) {
|
|
9222
9459
|
if (sampleAmount0.eq(0)) {
|
|
9223
9460
|
return {
|
|
9224
|
-
amount0: Web3Number.fromWei("0",
|
|
9461
|
+
amount0: Web3Number.fromWei("0", amount0.decimals),
|
|
9225
9462
|
amount1,
|
|
9226
9463
|
ratio: 0
|
|
9227
9464
|
};
|
|
9228
9465
|
} else if (sampleAmount1.eq(0)) {
|
|
9229
9466
|
return {
|
|
9230
9467
|
amount0: amount1.dividedBy(price),
|
|
9231
|
-
amount1: Web3Number.fromWei("0",
|
|
9468
|
+
amount1: Web3Number.fromWei("0", amount1.decimals),
|
|
9232
9469
|
ratio: Infinity
|
|
9233
9470
|
};
|
|
9234
9471
|
}
|
|
9235
9472
|
}
|
|
9236
|
-
|
|
9473
|
+
assert(sampleAmount0.decimals == sampleAmount1.decimals, "Sample amounts have different decimals");
|
|
9474
|
+
const ratioWeb3Number = sampleAmount0.multipliedBy(1e18).dividedBy(sampleAmount1.toString()).dividedBy(1e18);
|
|
9475
|
+
const ratio = Number(ratioWeb3Number.toFixed(18));
|
|
9237
9476
|
logger.verbose(`${_EkuboCLVault.name}: ${this.metadata.name} => ratio: ${ratio.toString()}`);
|
|
9238
|
-
|
|
9477
|
+
if (justUseInputAmount)
|
|
9478
|
+
return this._solveExpectedAmountsEq(amount0, amount1, ratioWeb3Number, price);
|
|
9479
|
+
if (amount1.eq(0) && amount0.greaterThan(0)) {
|
|
9480
|
+
const _amount1 = amount0.dividedBy(ratioWeb3Number);
|
|
9481
|
+
return {
|
|
9482
|
+
amount0,
|
|
9483
|
+
amount1: _amount1,
|
|
9484
|
+
ratio
|
|
9485
|
+
};
|
|
9486
|
+
} else if (amount0.eq(0) && amount1.greaterThan(0)) {
|
|
9487
|
+
const _amount0 = amount1.multipliedBy(ratio);
|
|
9488
|
+
return {
|
|
9489
|
+
amount0: _amount0,
|
|
9490
|
+
amount1,
|
|
9491
|
+
ratio
|
|
9492
|
+
};
|
|
9493
|
+
} else {
|
|
9494
|
+
throw new Error("Both amounts are non-zero, cannot compute expected amounts");
|
|
9495
|
+
}
|
|
9239
9496
|
}
|
|
9240
9497
|
_solveExpectedAmountsEq(availableAmount0, availableAmount1, ratio, price) {
|
|
9241
9498
|
const y = ratio.multipliedBy(availableAmount1).minus(availableAmount0).dividedBy(ratio.plus(1 / price));
|
|
@@ -9249,10 +9506,11 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9249
9506
|
async getSwapInfoToHandleUnused(considerRebalance = true) {
|
|
9250
9507
|
const poolKey = await this.getPoolKey();
|
|
9251
9508
|
const erc20Mod = new ERC20(this.config);
|
|
9252
|
-
const token0Bal1 = await erc20Mod.balanceOf(poolKey.token0, this.address.address, 18);
|
|
9253
|
-
const token1Bal1 = await erc20Mod.balanceOf(poolKey.token1, this.address.address, 18);
|
|
9254
9509
|
const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
|
|
9255
9510
|
const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
|
|
9511
|
+
const token0Bal1 = await erc20Mod.balanceOf(poolKey.token0, this.address.address, token0Info.decimals);
|
|
9512
|
+
const token1Bal1 = await erc20Mod.balanceOf(poolKey.token1, this.address.address, token1Info.decimals);
|
|
9513
|
+
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => token0Bal1: ${token0Bal1.toString()}, token1Bal1: ${token1Bal1.toString()}`);
|
|
9256
9514
|
const token0Price = await this.pricer.getPrice(token0Info.symbol);
|
|
9257
9515
|
const token1Price = await this.pricer.getPrice(token1Info.symbol);
|
|
9258
9516
|
const token0PriceUsd = token0Price.price * Number(token0Bal1.toFixed(13));
|
|
@@ -9263,6 +9521,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9263
9521
|
let token0Bal = token0Bal1;
|
|
9264
9522
|
let token1Bal = token1Bal1;
|
|
9265
9523
|
if (considerRebalance) {
|
|
9524
|
+
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => considerRebalance: true`);
|
|
9266
9525
|
const tvl = await this.getTVL();
|
|
9267
9526
|
token0Bal = token0Bal.plus(tvl.token0.amount.toString());
|
|
9268
9527
|
token1Bal = token1Bal.plus(tvl.token1.amount.toString());
|
|
@@ -9272,7 +9531,10 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9272
9531
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => token0Bal: ${token0Bal.toString()}, token1Bal: ${token1Bal.toString()}`);
|
|
9273
9532
|
const newBounds = await this.getNewBounds();
|
|
9274
9533
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => newBounds: ${newBounds.lowerTick}, ${newBounds.upperTick}`);
|
|
9275
|
-
|
|
9534
|
+
return await this.getSwapInfoGivenAmounts(poolKey, token0Bal, token1Bal, newBounds);
|
|
9535
|
+
}
|
|
9536
|
+
async getSwapInfoGivenAmounts(poolKey, token0Bal, token1Bal, bounds) {
|
|
9537
|
+
let expectedAmounts = await this._getExpectedAmountsForLiquidity(token0Bal, token1Bal, bounds);
|
|
9276
9538
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => expectedAmounts: ${expectedAmounts.amount0.toString()}, ${expectedAmounts.amount1.toString()}`);
|
|
9277
9539
|
let retry = 0;
|
|
9278
9540
|
const maxRetry = 10;
|
|
@@ -9293,6 +9555,19 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9293
9555
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => tokenToSell: ${tokenToSell.address}, tokenToBuy: ${tokenToBuy.address}, amountToSell: ${amountToSell.toWei()}`);
|
|
9294
9556
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => remainingSellAmount: ${remainingSellAmount.toString()}`);
|
|
9295
9557
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => expectedRatio: ${expectedRatio}`);
|
|
9558
|
+
if (amountToSell.eq(0)) {
|
|
9559
|
+
return {
|
|
9560
|
+
token_from_address: tokenToSell.address,
|
|
9561
|
+
token_from_amount: import_starknet9.uint256.bnToUint256(0),
|
|
9562
|
+
token_to_address: tokenToSell.address,
|
|
9563
|
+
token_to_amount: import_starknet9.uint256.bnToUint256(0),
|
|
9564
|
+
token_to_min_amount: import_starknet9.uint256.bnToUint256(0),
|
|
9565
|
+
beneficiary: this.address.address,
|
|
9566
|
+
integrator_fee_amount_bps: 0,
|
|
9567
|
+
integrator_fee_recipient: this.address.address,
|
|
9568
|
+
routes: []
|
|
9569
|
+
};
|
|
9570
|
+
}
|
|
9296
9571
|
const quote = await this.avnu.getQuotes(tokenToSell.address, tokenToBuy.address, amountToSell.toWei(), this.address.address);
|
|
9297
9572
|
if (remainingSellAmount.eq(0)) {
|
|
9298
9573
|
const minAmountOut = Web3Number.fromWei(quote.buyAmount.toString(), tokenToBuyInfo.decimals).multipliedBy(0.9999);
|
|
@@ -9315,6 +9590,73 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9315
9590
|
}
|
|
9316
9591
|
throw new Error("Failed to get swap info");
|
|
9317
9592
|
}
|
|
9593
|
+
/**
|
|
9594
|
+
* Attempts to rebalance the vault by iteratively adjusting swap amounts if initial attempt fails.
|
|
9595
|
+
* Uses binary search approach to find optimal swap amount.
|
|
9596
|
+
*
|
|
9597
|
+
* @param newBounds - The new tick bounds to rebalance to
|
|
9598
|
+
* @param swapInfo - Initial swap parameters for rebalancing
|
|
9599
|
+
* @param acc - Account to estimate gas fees with
|
|
9600
|
+
* @param retry - Current retry attempt number (default 0)
|
|
9601
|
+
* @param adjustmentFactor - Percentage to adjust swap amount by (default 1)
|
|
9602
|
+
* @param isToken0Deficit - Whether token0 balance needs increasing (default true)
|
|
9603
|
+
* @returns Array of contract calls needed for rebalancing
|
|
9604
|
+
* @throws Error if max retries reached without successful rebalance
|
|
9605
|
+
*/
|
|
9606
|
+
async rebalanceIter(swapInfo, acc, estimateCall, retry = 0, adjustmentFactor = 1, isToken0Deficit = true) {
|
|
9607
|
+
const MAX_RETRIES = 20;
|
|
9608
|
+
const MIN_ADJUSTMENT = 1e-3;
|
|
9609
|
+
logger.verbose(
|
|
9610
|
+
`Rebalancing ${this.metadata.name}: retry=${retry}, adjustment=${adjustmentFactor}%, token0Deficit=${isToken0Deficit}`
|
|
9611
|
+
);
|
|
9612
|
+
const fromAmount = import_starknet9.uint256.uint256ToBN(swapInfo.token_from_amount);
|
|
9613
|
+
logger.verbose(
|
|
9614
|
+
`Selling ${fromAmount.toString()} of token ${swapInfo.token_from_address}`
|
|
9615
|
+
);
|
|
9616
|
+
try {
|
|
9617
|
+
const calls = await estimateCall(swapInfo);
|
|
9618
|
+
await acc.estimateInvokeFee(calls);
|
|
9619
|
+
return calls;
|
|
9620
|
+
} catch (err) {
|
|
9621
|
+
if (retry >= MAX_RETRIES) {
|
|
9622
|
+
logger.error(`Rebalance failed after ${MAX_RETRIES} retries`);
|
|
9623
|
+
throw err;
|
|
9624
|
+
}
|
|
9625
|
+
if (adjustmentFactor < MIN_ADJUSTMENT) {
|
|
9626
|
+
logger.error("Adjustment factor too small, likely oscillating");
|
|
9627
|
+
throw new Error("Failed to converge on valid swap amount");
|
|
9628
|
+
}
|
|
9629
|
+
logger.error(`Rebalance attempt ${retry + 1} failed, adjusting swap amount...`);
|
|
9630
|
+
const newSwapInfo = { ...swapInfo };
|
|
9631
|
+
const currentAmount = Web3Number.fromWei(fromAmount.toString(), 18);
|
|
9632
|
+
if (err.message.includes("invalid token0 balance") || err.message.includes("invalid token0 amount")) {
|
|
9633
|
+
logger.verbose("Reducing swap amount - excess token0");
|
|
9634
|
+
newSwapInfo.token_from_amount = import_starknet9.uint256.bnToUint256(
|
|
9635
|
+
currentAmount.multipliedBy((100 - adjustmentFactor) / 100).toWei()
|
|
9636
|
+
);
|
|
9637
|
+
adjustmentFactor = isToken0Deficit ? adjustmentFactor * 2 : adjustmentFactor / 2;
|
|
9638
|
+
isToken0Deficit = true;
|
|
9639
|
+
} else if (err.message.includes("invalid token1 balance") || err.message.includes("invalid token1 amount")) {
|
|
9640
|
+
logger.verbose("Increasing swap amount - excess token1");
|
|
9641
|
+
newSwapInfo.token_from_amount = import_starknet9.uint256.bnToUint256(
|
|
9642
|
+
currentAmount.multipliedBy((100 + adjustmentFactor) / 100).toWei()
|
|
9643
|
+
);
|
|
9644
|
+
adjustmentFactor = isToken0Deficit ? adjustmentFactor / 2 : adjustmentFactor * 2;
|
|
9645
|
+
isToken0Deficit = false;
|
|
9646
|
+
} else {
|
|
9647
|
+
logger.error("Unexpected error:", err);
|
|
9648
|
+
}
|
|
9649
|
+
newSwapInfo.token_to_min_amount = import_starknet9.uint256.bnToUint256("0");
|
|
9650
|
+
return this.rebalanceIter(
|
|
9651
|
+
newSwapInfo,
|
|
9652
|
+
acc,
|
|
9653
|
+
estimateCall,
|
|
9654
|
+
retry + 1,
|
|
9655
|
+
adjustmentFactor,
|
|
9656
|
+
isToken0Deficit
|
|
9657
|
+
);
|
|
9658
|
+
}
|
|
9659
|
+
}
|
|
9318
9660
|
static tickToi129(tick) {
|
|
9319
9661
|
if (tick < 0) {
|
|
9320
9662
|
return {
|
|
@@ -9337,21 +9679,23 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9337
9679
|
static tickToPrice(tick) {
|
|
9338
9680
|
return Math.pow(1.000001, Number(tick));
|
|
9339
9681
|
}
|
|
9340
|
-
async getLiquidityToAmounts(liquidity, bounds) {
|
|
9341
|
-
const currentPrice = await this.getCurrentPrice();
|
|
9342
|
-
const lowerPrice =
|
|
9343
|
-
const upperPrice =
|
|
9682
|
+
async getLiquidityToAmounts(liquidity, bounds, blockIdentifier = "pending", _poolKey = null, _currentPrice = null) {
|
|
9683
|
+
const currentPrice = _currentPrice || await this.getCurrentPrice(blockIdentifier);
|
|
9684
|
+
const lowerPrice = _EkuboCLVault.tickToPrice(bounds.lowerTick);
|
|
9685
|
+
const upperPrice = _EkuboCLVault.tickToPrice(bounds.upperTick);
|
|
9344
9686
|
logger.verbose(`${_EkuboCLVault.name}: getLiquidityToAmounts => currentPrice: ${currentPrice.price}, lowerPrice: ${lowerPrice}, upperPrice: ${upperPrice}`);
|
|
9345
9687
|
const result = await this.ekuboMathContract.call("liquidity_delta_to_amount_delta", [
|
|
9346
|
-
|
|
9688
|
+
import_starknet9.uint256.bnToUint256(currentPrice.sqrtRatio),
|
|
9347
9689
|
{
|
|
9348
9690
|
mag: liquidity.toWei(),
|
|
9349
9691
|
sign: 0
|
|
9350
9692
|
},
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
]
|
|
9354
|
-
|
|
9693
|
+
import_starknet9.uint256.bnToUint256(_EkuboCLVault.priceToSqrtRatio(lowerPrice).toString()),
|
|
9694
|
+
import_starknet9.uint256.bnToUint256(_EkuboCLVault.priceToSqrtRatio(upperPrice).toString())
|
|
9695
|
+
], {
|
|
9696
|
+
blockIdentifier
|
|
9697
|
+
});
|
|
9698
|
+
const poolKey = _poolKey || await this.getPoolKey(blockIdentifier);
|
|
9355
9699
|
const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
|
|
9356
9700
|
const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
|
|
9357
9701
|
const amount0 = Web3Number.fromWei(_EkuboCLVault.i129ToNumber(result.amount0).toString(), token0Info.decimals);
|
|
@@ -9361,32 +9705,110 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9361
9705
|
amount1
|
|
9362
9706
|
};
|
|
9363
9707
|
}
|
|
9708
|
+
async harvest(acc) {
|
|
9709
|
+
const ekuboHarvests = new EkuboHarvests(this.config);
|
|
9710
|
+
const unClaimedRewards = await ekuboHarvests.getUnHarvestedRewards(this.address);
|
|
9711
|
+
const poolKey = await this.getPoolKey();
|
|
9712
|
+
const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
|
|
9713
|
+
const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
|
|
9714
|
+
const bounds = await this.getCurrentBounds();
|
|
9715
|
+
const calls = [];
|
|
9716
|
+
for (let claim of unClaimedRewards) {
|
|
9717
|
+
const fee = claim.claim.amount.multipliedBy(this.metadata.additionalInfo.feeBps).dividedBy(1e4);
|
|
9718
|
+
const postFeeAmount = claim.claim.amount.minus(fee);
|
|
9719
|
+
const isToken1 = claim.token.eq(poolKey.token1);
|
|
9720
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => Processing claim, isToken1: ${isToken1} amount: ${postFeeAmount.toWei()}`);
|
|
9721
|
+
const token0Amt = isToken1 ? new Web3Number(0, token0Info.decimals) : postFeeAmount;
|
|
9722
|
+
const token1Amt = isToken1 ? postFeeAmount : new Web3Number(0, token0Info.decimals);
|
|
9723
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => token0Amt: ${token0Amt.toString()}, token1Amt: ${token1Amt.toString()}`);
|
|
9724
|
+
const swapInfo = await this.getSwapInfoGivenAmounts(poolKey, token0Amt, token1Amt, bounds);
|
|
9725
|
+
swapInfo.token_to_address = token0Info.address.address;
|
|
9726
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => swapInfo: ${JSON.stringify(swapInfo)}`);
|
|
9727
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => claim: ${JSON.stringify(claim)}`);
|
|
9728
|
+
const harvestEstimateCall = async (swapInfo1) => {
|
|
9729
|
+
const swap1Amount = Web3Number.fromWei(import_starknet9.uint256.uint256ToBN(swapInfo1.token_from_amount).toString(), 18);
|
|
9730
|
+
const remainingAmount = postFeeAmount.minus(swap1Amount);
|
|
9731
|
+
const swapInfo2 = { ...swapInfo, token_from_amount: import_starknet9.uint256.bnToUint256(remainingAmount.toWei()) };
|
|
9732
|
+
swapInfo2.token_to_address = token1Info.address.address;
|
|
9733
|
+
const calldata = [
|
|
9734
|
+
claim.rewardsContract.address,
|
|
9735
|
+
{
|
|
9736
|
+
id: claim.claim.id,
|
|
9737
|
+
amount: claim.claim.amount.toWei(),
|
|
9738
|
+
claimee: claim.claim.claimee.address
|
|
9739
|
+
},
|
|
9740
|
+
claim.proof.map((p) => import_starknet9.num.getDecimalString(p)),
|
|
9741
|
+
swapInfo,
|
|
9742
|
+
swapInfo2
|
|
9743
|
+
];
|
|
9744
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => calldata: ${JSON.stringify(calldata)}`);
|
|
9745
|
+
return [this.contract.populate("harvest", calldata)];
|
|
9746
|
+
};
|
|
9747
|
+
const _callsFinal = await this.rebalanceIter(swapInfo, acc, harvestEstimateCall);
|
|
9748
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => _callsFinal: ${JSON.stringify(_callsFinal)}`);
|
|
9749
|
+
calls.push(..._callsFinal);
|
|
9750
|
+
}
|
|
9751
|
+
return calls;
|
|
9752
|
+
}
|
|
9753
|
+
async getInvestmentFlows() {
|
|
9754
|
+
const netYield = await this.netAPY();
|
|
9755
|
+
const poolKey = await this.getPoolKey();
|
|
9756
|
+
const linkedFlow = {
|
|
9757
|
+
title: this.metadata.name,
|
|
9758
|
+
subItems: [{ key: "Pool", value: `${(_EkuboCLVault.div2Power128(BigInt(poolKey.fee)) * 100).toFixed(2)}%, ${poolKey.tick_spacing} tick spacing` }],
|
|
9759
|
+
linkedFlows: [],
|
|
9760
|
+
style: { backgroundColor: "#35484f" /* Blue */.valueOf() }
|
|
9761
|
+
};
|
|
9762
|
+
const baseFlow = {
|
|
9763
|
+
id: "base",
|
|
9764
|
+
title: "Your Deposit",
|
|
9765
|
+
subItems: [{ key: `Net yield`, value: `${(netYield * 100).toFixed(2)}%` }, { key: `Performance Fee`, value: `${(this.metadata.additionalInfo.feeBps / 100).toFixed(2)}%` }],
|
|
9766
|
+
linkedFlows: [linkedFlow],
|
|
9767
|
+
style: { backgroundColor: "#6e53dc" /* Purple */.valueOf() }
|
|
9768
|
+
};
|
|
9769
|
+
const rebalanceFlow = {
|
|
9770
|
+
id: "rebalance",
|
|
9771
|
+
title: "Automated Rebalance",
|
|
9772
|
+
subItems: [{
|
|
9773
|
+
key: "Range selection",
|
|
9774
|
+
value: `${this.metadata.additionalInfo.newBounds.lower * Number(poolKey.tick_spacing)} to ${this.metadata.additionalInfo.newBounds.upper * Number(poolKey.tick_spacing)} ticks`
|
|
9775
|
+
}],
|
|
9776
|
+
linkedFlows: [linkedFlow],
|
|
9777
|
+
style: { backgroundColor: "purple" /* Green */.valueOf() }
|
|
9778
|
+
};
|
|
9779
|
+
return [baseFlow, rebalanceFlow];
|
|
9780
|
+
}
|
|
9364
9781
|
};
|
|
9365
|
-
var _description2 = "
|
|
9782
|
+
var _description2 = "Deploys your {{POOL_NAME}} into an Ekubo liquidity pool, automatically rebalancing positions around the current price to optimize yield and reduce the need for manual adjustments. Trading fees and DeFi Spring rewards are automatically compounded back into the strategy. In return, you receive an ERC-20 token representing your share of the strategy. The APY is calculated based on 7-day historical performance.";
|
|
9366
9783
|
var _protocol2 = { name: "Ekubo", logo: "https://app.ekubo.org/favicon.ico" };
|
|
9367
9784
|
var _riskFactor2 = [
|
|
9368
9785
|
{ type: "Smart Contract Risk" /* SMART_CONTRACT_RISK */, value: 0.5, weight: 25 },
|
|
9369
9786
|
{ type: "Impermanent Loss Risk" /* IMPERMANENT_LOSS */, value: 1, weight: 75 }
|
|
9370
9787
|
];
|
|
9788
|
+
var AUDIT_URL2 = "https://assets.strkfarm.com/strkfarm/audit_report_vesu_and_ekubo_strats.pdf";
|
|
9371
9789
|
var EkuboCLVaultStrategies = [{
|
|
9372
9790
|
name: "Ekubo xSTRK/STRK",
|
|
9373
|
-
description: _description2,
|
|
9791
|
+
description: _description2.replace("{{POOL_NAME}}", "xSTRK/STRK"),
|
|
9374
9792
|
address: ContractAddr.from("0x01f083b98674bc21effee29ef443a00c7b9a500fd92cf30341a3da12c73f2324"),
|
|
9375
9793
|
type: "Other",
|
|
9376
|
-
|
|
9794
|
+
// must be same order as poolKey token0 and token1
|
|
9795
|
+
depositTokens: [Global.getDefaultTokens().find((t) => t.symbol === "xSTRK"), Global.getDefaultTokens().find((t) => t.symbol === "STRK")],
|
|
9377
9796
|
protocols: [_protocol2],
|
|
9797
|
+
auditUrl: AUDIT_URL2,
|
|
9378
9798
|
maxTVL: Web3Number.fromWei("0", 18),
|
|
9379
9799
|
risk: {
|
|
9380
9800
|
riskFactor: _riskFactor2,
|
|
9381
9801
|
netRisk: _riskFactor2.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / _riskFactor2.reduce((acc, curr) => acc + curr.weight, 0),
|
|
9382
9802
|
notARisks: getNoRiskTags(_riskFactor2)
|
|
9383
9803
|
},
|
|
9804
|
+
apyMethodology: "APY based on 7-day historical performance, including fees and rewards.",
|
|
9384
9805
|
additionalInfo: {
|
|
9385
9806
|
newBounds: {
|
|
9386
9807
|
lower: -1,
|
|
9387
9808
|
upper: 1
|
|
9388
9809
|
},
|
|
9389
|
-
lstContract: ContractAddr.from("0x028d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a")
|
|
9810
|
+
lstContract: ContractAddr.from("0x028d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a"),
|
|
9811
|
+
feeBps: 1e3
|
|
9390
9812
|
}
|
|
9391
9813
|
}];
|
|
9392
9814
|
|
|
@@ -9496,7 +9918,7 @@ var PricerRedis = class extends Pricer {
|
|
|
9496
9918
|
|
|
9497
9919
|
// src/utils/store.ts
|
|
9498
9920
|
var import_fs = __toESM(require("fs"));
|
|
9499
|
-
var
|
|
9921
|
+
var import_starknet10 = require("starknet");
|
|
9500
9922
|
var crypto2 = __toESM(require("crypto"));
|
|
9501
9923
|
|
|
9502
9924
|
// src/utils/encrypt.ts
|
|
@@ -9581,7 +10003,7 @@ var Store = class _Store {
|
|
|
9581
10003
|
logger.warn(`This not stored anywhere, please you backup this password for future use`);
|
|
9582
10004
|
logger.warn(`\u26A0\uFE0F=========================================\u26A0\uFE0F`);
|
|
9583
10005
|
}
|
|
9584
|
-
getAccount(accountKey, txVersion =
|
|
10006
|
+
getAccount(accountKey, txVersion = import_starknet10.constants.TRANSACTION_VERSION.V2) {
|
|
9585
10007
|
const accounts = this.loadAccounts();
|
|
9586
10008
|
logger.verbose(`nAccounts loaded for network: ${Object.keys(accounts).length}`);
|
|
9587
10009
|
const data = accounts[accountKey];
|
|
@@ -9590,7 +10012,7 @@ var Store = class _Store {
|
|
|
9590
10012
|
}
|
|
9591
10013
|
logger.verbose(`Account loaded: ${accountKey} from network: ${this.config.network}`);
|
|
9592
10014
|
logger.verbose(`Address: ${data.address}`);
|
|
9593
|
-
const acc = new
|
|
10015
|
+
const acc = new import_starknet10.Account(this.config.provider, data.address, data.pk, void 0, txVersion);
|
|
9594
10016
|
return acc;
|
|
9595
10017
|
}
|
|
9596
10018
|
addAccount(accountKey, address, pk) {
|
|
@@ -9655,6 +10077,7 @@ var Store = class _Store {
|
|
|
9655
10077
|
0 && (module.exports = {
|
|
9656
10078
|
AutoCompounderSTRK,
|
|
9657
10079
|
AvnuWrapper,
|
|
10080
|
+
BaseStrategy,
|
|
9658
10081
|
ContractAddr,
|
|
9659
10082
|
ERC20,
|
|
9660
10083
|
EkuboCLVault,
|