@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.mjs
CHANGED
|
@@ -18,19 +18,19 @@ var _Web3Number = class extends BigNumber {
|
|
|
18
18
|
return this.mul(10 ** this.decimals).toFixed(0);
|
|
19
19
|
}
|
|
20
20
|
multipliedBy(value) {
|
|
21
|
-
|
|
21
|
+
const _value = this.getStandardString(value);
|
|
22
22
|
return this.construct(this.mul(_value).toString(), this.decimals);
|
|
23
23
|
}
|
|
24
24
|
dividedBy(value) {
|
|
25
|
-
|
|
25
|
+
const _value = this.getStandardString(value);
|
|
26
26
|
return this.construct(this.div(_value).toString(), this.decimals);
|
|
27
27
|
}
|
|
28
28
|
plus(value) {
|
|
29
|
-
const _value =
|
|
29
|
+
const _value = this.getStandardString(value);
|
|
30
30
|
return this.construct(this.add(_value).toString(), this.decimals);
|
|
31
31
|
}
|
|
32
32
|
minus(n, base) {
|
|
33
|
-
const _value =
|
|
33
|
+
const _value = this.getStandardString(n);
|
|
34
34
|
return this.construct(super.minus(_value, base).toString(), this.decimals);
|
|
35
35
|
}
|
|
36
36
|
construct(value, decimals) {
|
|
@@ -46,11 +46,17 @@ var _Web3Number = class extends BigNumber {
|
|
|
46
46
|
return this.toString();
|
|
47
47
|
}
|
|
48
48
|
maxToFixedDecimals() {
|
|
49
|
-
return Math.min(this.decimals,
|
|
49
|
+
return Math.min(this.decimals, 18);
|
|
50
|
+
}
|
|
51
|
+
getStandardString(value) {
|
|
52
|
+
if (typeof value == "string") {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
return value.toFixed(this.maxToFixedDecimals());
|
|
50
56
|
}
|
|
51
57
|
};
|
|
52
|
-
BigNumber.config({ DECIMAL_PLACES: 18 });
|
|
53
|
-
_Web3Number.config({ DECIMAL_PLACES: 18 });
|
|
58
|
+
BigNumber.config({ DECIMAL_PLACES: 18, ROUNDING_MODE: BigNumber.ROUND_DOWN });
|
|
59
|
+
_Web3Number.config({ DECIMAL_PLACES: 18, ROUNDING_MODE: BigNumber.ROUND_DOWN });
|
|
54
60
|
|
|
55
61
|
// src/dataTypes/bignumber.node.ts
|
|
56
62
|
var Web3Number = class _Web3Number2 extends _Web3Number {
|
|
@@ -122,42 +128,48 @@ var defaultTokens = [{
|
|
|
122
128
|
logo: "https://assets.coingecko.com/coins/images/26433/small/starknet.png",
|
|
123
129
|
address: ContractAddr.from("0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d"),
|
|
124
130
|
decimals: 18,
|
|
125
|
-
coingeckId: "starknet"
|
|
131
|
+
coingeckId: "starknet",
|
|
132
|
+
displayDecimals: 2
|
|
126
133
|
}, {
|
|
127
134
|
name: "xSTRK",
|
|
128
135
|
symbol: "xSTRK",
|
|
129
136
|
logo: "https://dashboard.endur.fi/endur-fi.svg",
|
|
130
137
|
address: ContractAddr.from("0x028d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a"),
|
|
131
138
|
decimals: 18,
|
|
132
|
-
coingeckId: void 0
|
|
139
|
+
coingeckId: void 0,
|
|
140
|
+
displayDecimals: 2
|
|
133
141
|
}, {
|
|
134
142
|
name: "ETH",
|
|
135
143
|
symbol: "ETH",
|
|
136
144
|
logo: "https://opbnb.bscscan.com/token/images/ether.svg",
|
|
137
145
|
address: ContractAddr.from("0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"),
|
|
138
146
|
decimals: 18,
|
|
139
|
-
coingeckId: void 0
|
|
147
|
+
coingeckId: void 0,
|
|
148
|
+
displayDecimals: 4
|
|
140
149
|
}, {
|
|
141
150
|
name: "USDC",
|
|
142
151
|
symbol: "USDC",
|
|
143
152
|
logo: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
|
|
144
153
|
address: ContractAddr.from("0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"),
|
|
145
154
|
decimals: 6,
|
|
146
|
-
coingeckId: void 0
|
|
155
|
+
coingeckId: void 0,
|
|
156
|
+
displayDecimals: 2
|
|
147
157
|
}, {
|
|
148
158
|
name: "USDT",
|
|
149
159
|
symbol: "USDT",
|
|
150
160
|
logo: "https://assets.coingecko.com/coins/images/325/small/Tether.png",
|
|
151
161
|
address: ContractAddr.from("0x68f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"),
|
|
152
162
|
decimals: 6,
|
|
153
|
-
coingeckId: void 0
|
|
163
|
+
coingeckId: void 0,
|
|
164
|
+
displayDecimals: 2
|
|
154
165
|
}, {
|
|
155
166
|
name: "WBTC",
|
|
156
167
|
symbol: "WBTC",
|
|
157
168
|
logo: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599/logo.png",
|
|
158
169
|
address: ContractAddr.from("0x3fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac"),
|
|
159
170
|
decimals: 8,
|
|
160
|
-
coingeckId: void 0
|
|
171
|
+
coingeckId: void 0,
|
|
172
|
+
displayDecimals: 6
|
|
161
173
|
}];
|
|
162
174
|
var tokens = defaultTokens;
|
|
163
175
|
var Global = class _Global {
|
|
@@ -189,7 +201,8 @@ var Global = class _Global {
|
|
|
189
201
|
address: ContractAddr.from(token.address),
|
|
190
202
|
decimals: token.decimals,
|
|
191
203
|
logo: token.logoUri,
|
|
192
|
-
coingeckId: token.extensions.coingeckoId
|
|
204
|
+
coingeckId: token.extensions.coingeckoId,
|
|
205
|
+
displayDecimals: 2
|
|
193
206
|
});
|
|
194
207
|
});
|
|
195
208
|
console.log(tokens);
|
|
@@ -573,7 +586,8 @@ var _ZkLend = class _ZkLend extends ILending {
|
|
|
573
586
|
logo: "",
|
|
574
587
|
decimals: pool.token.decimals,
|
|
575
588
|
borrowFactor: Web3Number2.fromWei(pool.borrow_factor.value, pool.borrow_factor.decimals),
|
|
576
|
-
collareralFactor
|
|
589
|
+
collareralFactor,
|
|
590
|
+
displayDecimals: 2
|
|
577
591
|
};
|
|
578
592
|
this.tokens.push(token);
|
|
579
593
|
});
|
|
@@ -690,7 +704,7 @@ var PricerFromApi = class extends PricerBase {
|
|
|
690
704
|
try {
|
|
691
705
|
return await this.getPriceFromMyAPI(tokenSymbol);
|
|
692
706
|
} catch (e) {
|
|
693
|
-
logger.warn("getPriceFromMyAPI error", e);
|
|
707
|
+
logger.warn("getPriceFromMyAPI error", JSON.stringify(e.message || e));
|
|
694
708
|
}
|
|
695
709
|
logger.log("getPrice coinbase", tokenSymbol);
|
|
696
710
|
let retry = 0;
|
|
@@ -710,7 +724,7 @@ var PricerFromApi = class extends PricerBase {
|
|
|
710
724
|
timestamp: /* @__PURE__ */ new Date()
|
|
711
725
|
};
|
|
712
726
|
} catch (e) {
|
|
713
|
-
logger.warn("getPrice coinbase error", e
|
|
727
|
+
logger.warn("getPrice coinbase error", JSON.stringify(e.message || e));
|
|
714
728
|
await new Promise((resolve) => setTimeout(resolve, retry * 1e3));
|
|
715
729
|
}
|
|
716
730
|
}
|
|
@@ -724,9 +738,6 @@ var PricerFromApi = class extends PricerBase {
|
|
|
724
738
|
const priceInfo = await priceInfoRes.json();
|
|
725
739
|
const now = /* @__PURE__ */ new Date();
|
|
726
740
|
const priceTime = new Date(priceInfo.timestamp);
|
|
727
|
-
if (now.getTime() - priceTime.getTime() > 9e5) {
|
|
728
|
-
throw new Error("Price is stale");
|
|
729
|
-
}
|
|
730
741
|
const price = Number(priceInfo.price);
|
|
731
742
|
return {
|
|
732
743
|
price,
|
|
@@ -1890,17 +1901,28 @@ function assert(condition, message) {
|
|
|
1890
1901
|
}
|
|
1891
1902
|
|
|
1892
1903
|
// src/modules/avnu.ts
|
|
1893
|
-
var AvnuWrapper = class {
|
|
1894
|
-
async getQuotes(fromToken, toToken, amountWei, taker) {
|
|
1904
|
+
var AvnuWrapper = class _AvnuWrapper {
|
|
1905
|
+
async getQuotes(fromToken, toToken, amountWei, taker, retry = 0) {
|
|
1906
|
+
const MAX_RETRY = 5;
|
|
1907
|
+
logger.verbose(`${_AvnuWrapper.name}: getQuotes => Getting quotes for ${fromToken} -> ${toToken}, amount: ${amountWei}, taker: ${taker}, retry: ${retry}`);
|
|
1895
1908
|
const params = {
|
|
1896
1909
|
sellTokenAddress: fromToken,
|
|
1897
1910
|
buyTokenAddress: toToken,
|
|
1898
1911
|
sellAmount: amountWei,
|
|
1899
|
-
takerAddress: taker
|
|
1912
|
+
takerAddress: taker,
|
|
1913
|
+
// excludeSources: ['Nostra', 'Haiko(Solvers)']
|
|
1914
|
+
excludeSources: ["Haiko(Solvers)"]
|
|
1915
|
+
// to resolve InvalidOraclePrice error
|
|
1900
1916
|
};
|
|
1901
1917
|
assert(fromToken != toToken, "From and to tokens are the same");
|
|
1902
1918
|
const quotes = await fetchQuotes(params);
|
|
1903
|
-
|
|
1919
|
+
if (quotes.length == 0) {
|
|
1920
|
+
if (retry < MAX_RETRY) {
|
|
1921
|
+
await new Promise((res) => setTimeout(res, 3e3));
|
|
1922
|
+
return await this.getQuotes(fromToken, toToken, amountWei, taker, retry + 1);
|
|
1923
|
+
}
|
|
1924
|
+
throw new Error("no quotes found");
|
|
1925
|
+
}
|
|
1904
1926
|
return quotes[0];
|
|
1905
1927
|
}
|
|
1906
1928
|
async getSwapInfo(quote, taker, integratorFeeBps, integratorFeeRecipient, minAmount) {
|
|
@@ -3573,10 +3595,10 @@ var BaseStrategy = class {
|
|
|
3573
3595
|
async getTVL() {
|
|
3574
3596
|
throw new Error("Not implemented");
|
|
3575
3597
|
}
|
|
3576
|
-
depositCall(amountInfo, receiver) {
|
|
3598
|
+
async depositCall(amountInfo, receiver) {
|
|
3577
3599
|
throw new Error("Not implemented");
|
|
3578
3600
|
}
|
|
3579
|
-
withdrawCall(amountInfo, receiver, owner) {
|
|
3601
|
+
async withdrawCall(amountInfo, receiver, owner) {
|
|
3580
3602
|
throw new Error("Not implemented");
|
|
3581
3603
|
}
|
|
3582
3604
|
};
|
|
@@ -3613,7 +3635,7 @@ var VesuRebalance = class _VesuRebalance extends BaseStrategy {
|
|
|
3613
3635
|
* @param receiver - Address that will receive the strategy tokens
|
|
3614
3636
|
* @returns Populated contract call for deposit
|
|
3615
3637
|
*/
|
|
3616
|
-
depositCall(amountInfo, receiver) {
|
|
3638
|
+
async depositCall(amountInfo, receiver) {
|
|
3617
3639
|
assert(amountInfo.tokenInfo.address.eq(this.asset().address), "Deposit token mismatch");
|
|
3618
3640
|
const assetContract = new Contract4(vesu_rebalance_abi_default, this.asset().address.address, this.config.provider);
|
|
3619
3641
|
const call1 = assetContract.populate("approve", [this.address.address, uint2563.bnToUint256(amountInfo.amount.toWei())]);
|
|
@@ -3627,7 +3649,7 @@ var VesuRebalance = class _VesuRebalance extends BaseStrategy {
|
|
|
3627
3649
|
* @param owner - Address that owns the strategy tokens
|
|
3628
3650
|
* @returns Populated contract call for withdrawal
|
|
3629
3651
|
*/
|
|
3630
|
-
withdrawCall(amountInfo, receiver, owner) {
|
|
3652
|
+
async withdrawCall(amountInfo, receiver, owner) {
|
|
3631
3653
|
return [this.contract.populate("withdraw", [uint2563.bnToUint256(amountInfo.amount.toWei()), receiver.address, owner.address])];
|
|
3632
3654
|
}
|
|
3633
3655
|
/**
|
|
@@ -4030,7 +4052,7 @@ var VesuRebalanceStrategies = [{
|
|
|
4030
4052
|
}];
|
|
4031
4053
|
|
|
4032
4054
|
// src/strategies/ekubo-cl-vault.ts
|
|
4033
|
-
import { Contract as
|
|
4055
|
+
import { Contract as Contract6, num as num3, uint256 as uint2564 } from "starknet";
|
|
4034
4056
|
|
|
4035
4057
|
// src/data/cl-vault.abi.json
|
|
4036
4058
|
var cl_vault_abi_default = [
|
|
@@ -8931,6 +8953,58 @@ var erc4626_abi_default = [
|
|
|
8931
8953
|
}
|
|
8932
8954
|
];
|
|
8933
8955
|
|
|
8956
|
+
// src/modules/harvests.ts
|
|
8957
|
+
import { Contract as Contract5 } from "starknet";
|
|
8958
|
+
var Harvests = class _Harvests {
|
|
8959
|
+
constructor(config) {
|
|
8960
|
+
this.config = config;
|
|
8961
|
+
}
|
|
8962
|
+
getHarvests(addr) {
|
|
8963
|
+
throw new Error("Not implemented");
|
|
8964
|
+
}
|
|
8965
|
+
async getUnHarvestedRewards(addr) {
|
|
8966
|
+
const rewards = await this.getHarvests(addr);
|
|
8967
|
+
if (rewards.length == 0) return [];
|
|
8968
|
+
const unClaimed = [];
|
|
8969
|
+
const cls = await this.config.provider.getClassAt(rewards[0].rewardsContract.address);
|
|
8970
|
+
for (let reward of rewards) {
|
|
8971
|
+
const contract = new Contract5(cls.abi, reward.rewardsContract.address, this.config.provider);
|
|
8972
|
+
const isClaimed = await contract.call("is_claimed", [reward.claim.id]);
|
|
8973
|
+
logger.verbose(`${_Harvests.name}: isClaimed: ${isClaimed}`);
|
|
8974
|
+
if (isClaimed)
|
|
8975
|
+
return unClaimed;
|
|
8976
|
+
unClaimed.unshift(reward);
|
|
8977
|
+
}
|
|
8978
|
+
return unClaimed;
|
|
8979
|
+
}
|
|
8980
|
+
};
|
|
8981
|
+
var EkuboHarvests = class extends Harvests {
|
|
8982
|
+
async getHarvests(addr) {
|
|
8983
|
+
const STRK = "0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
|
|
8984
|
+
const EKUBO_API = `https://starknet-mainnet-api.ekubo.org/airdrops/${addr.address}?token=${STRK}`;
|
|
8985
|
+
const resultEkubo = await fetch(EKUBO_API);
|
|
8986
|
+
const items = await resultEkubo.json();
|
|
8987
|
+
const rewards = [];
|
|
8988
|
+
for (let i = 0; i < items.length; ++i) {
|
|
8989
|
+
const info = items[i];
|
|
8990
|
+
assert(info.token == STRK, "expected strk token only");
|
|
8991
|
+
rewards.push({
|
|
8992
|
+
rewardsContract: ContractAddr.from(info.contract_address),
|
|
8993
|
+
token: ContractAddr.from(STRK),
|
|
8994
|
+
startDate: new Date(info.start_date),
|
|
8995
|
+
endDate: new Date(info.end_date),
|
|
8996
|
+
claim: {
|
|
8997
|
+
id: info.claim.id,
|
|
8998
|
+
amount: Web3Number.fromWei(info.claim.amount, 18),
|
|
8999
|
+
claimee: ContractAddr.from(info.claim.claimee)
|
|
9000
|
+
},
|
|
9001
|
+
proof: info.proof
|
|
9002
|
+
});
|
|
9003
|
+
}
|
|
9004
|
+
return rewards.sort((a, b) => b.endDate.getTime() - a.endDate.getTime());
|
|
9005
|
+
}
|
|
9006
|
+
};
|
|
9007
|
+
|
|
8934
9008
|
// src/strategies/ekubo-cl-vault.ts
|
|
8935
9009
|
var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
8936
9010
|
/**
|
|
@@ -8947,19 +9021,74 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
8947
9021
|
assert(metadata.depositTokens.length === 2, "EkuboCL only supports 2 deposit token");
|
|
8948
9022
|
this.metadata = metadata;
|
|
8949
9023
|
this.address = metadata.address;
|
|
8950
|
-
this.contract = new
|
|
8951
|
-
this.lstContract = new
|
|
9024
|
+
this.contract = new Contract6(cl_vault_abi_default, this.address.address, this.config.provider);
|
|
9025
|
+
this.lstContract = new Contract6(erc4626_abi_default, this.metadata.additionalInfo.lstContract.address, this.config.provider);
|
|
8952
9026
|
const EKUBO_POSITION = "0x02e0af29598b407c8716b17f6d2795eca1b471413fa03fb145a5e33722184067";
|
|
8953
|
-
this.ekuboPositionsContract = new
|
|
9027
|
+
this.ekuboPositionsContract = new Contract6(ekubo_positions_abi_default, EKUBO_POSITION, this.config.provider);
|
|
8954
9028
|
const EKUBO_MATH = "0x04a72e9e166f6c0e9d800af4dc40f6b6fb4404b735d3f528d9250808b2481995";
|
|
8955
|
-
this.ekuboMathContract = new
|
|
9029
|
+
this.ekuboMathContract = new Contract6(ekubo_math_abi_default, EKUBO_MATH, this.config.provider);
|
|
8956
9030
|
this.avnu = new AvnuWrapper();
|
|
8957
9031
|
}
|
|
8958
|
-
|
|
8959
|
-
|
|
9032
|
+
async matchInputAmounts(amountInfo) {
|
|
9033
|
+
const bounds = await this.getCurrentBounds();
|
|
9034
|
+
const res = await this._getExpectedAmountsForLiquidity(
|
|
9035
|
+
amountInfo.token0.amount,
|
|
9036
|
+
amountInfo.token1.amount,
|
|
9037
|
+
bounds,
|
|
9038
|
+
false
|
|
9039
|
+
);
|
|
9040
|
+
return {
|
|
9041
|
+
token0: {
|
|
9042
|
+
tokenInfo: amountInfo.token0.tokenInfo,
|
|
9043
|
+
amount: res.amount0
|
|
9044
|
+
},
|
|
9045
|
+
token1: {
|
|
9046
|
+
tokenInfo: amountInfo.token1.tokenInfo,
|
|
9047
|
+
amount: res.amount1
|
|
9048
|
+
}
|
|
9049
|
+
};
|
|
9050
|
+
}
|
|
9051
|
+
/** Returns minimum amounts give given two amounts based on what can be added for liq */
|
|
9052
|
+
async getMinDepositAmounts(amountInfo) {
|
|
9053
|
+
const shares = await this.tokensToShares(amountInfo);
|
|
9054
|
+
const { amount0, amount1 } = await this.contract.call("convert_to_assets", [uint2564.bnToUint256(shares.toWei())]);
|
|
9055
|
+
return {
|
|
9056
|
+
token0: {
|
|
9057
|
+
tokenInfo: amountInfo.token0.tokenInfo,
|
|
9058
|
+
amount: Web3Number.fromWei(amount0.toString(), amountInfo.token0.tokenInfo.decimals)
|
|
9059
|
+
},
|
|
9060
|
+
token1: {
|
|
9061
|
+
tokenInfo: amountInfo.token1.tokenInfo,
|
|
9062
|
+
amount: Web3Number.fromWei(amount1.toString(), amountInfo.token1.tokenInfo.decimals)
|
|
9063
|
+
}
|
|
9064
|
+
};
|
|
8960
9065
|
}
|
|
8961
|
-
|
|
8962
|
-
|
|
9066
|
+
async depositCall(amountInfo, receiver) {
|
|
9067
|
+
const updateAmountInfo = await this.getMinDepositAmounts(amountInfo);
|
|
9068
|
+
const token0Contract = new Contract6(erc4626_abi_default, amountInfo.token0.tokenInfo.address.address, this.config.provider);
|
|
9069
|
+
const token1Contract = new Contract6(erc4626_abi_default, amountInfo.token1.tokenInfo.address.address, this.config.provider);
|
|
9070
|
+
const call1 = token0Contract.populate("approve", [this.address.address, uint2564.bnToUint256(updateAmountInfo.token0.amount.toWei())]);
|
|
9071
|
+
const call2 = token1Contract.populate("approve", [this.address.address, uint2564.bnToUint256(updateAmountInfo.token1.amount.toWei())]);
|
|
9072
|
+
const call3 = this.contract.populate("deposit", [uint2564.bnToUint256(updateAmountInfo.token0.amount.toWei()), uint2564.bnToUint256(updateAmountInfo.token1.amount.toWei()), receiver.address]);
|
|
9073
|
+
const calls = [];
|
|
9074
|
+
if (updateAmountInfo.token0.amount.greaterThan(0)) calls.push(call1);
|
|
9075
|
+
if (updateAmountInfo.token1.amount.greaterThan(0)) calls.push(call2);
|
|
9076
|
+
return [...calls, call3];
|
|
9077
|
+
}
|
|
9078
|
+
async tokensToShares(amountInfo) {
|
|
9079
|
+
const shares = await this.contract.call("convert_to_shares", [
|
|
9080
|
+
uint2564.bnToUint256(amountInfo.token0.amount.toWei()),
|
|
9081
|
+
uint2564.bnToUint256(amountInfo.token1.amount.toWei())
|
|
9082
|
+
]);
|
|
9083
|
+
return Web3Number.fromWei(shares.toString(), 18);
|
|
9084
|
+
}
|
|
9085
|
+
async withdrawCall(amountInfo, receiver, owner) {
|
|
9086
|
+
const shares = await this.tokensToShares(amountInfo);
|
|
9087
|
+
logger.verbose(`${_EkuboCLVault.name}: withdrawCall: shares=${shares.toString()}`);
|
|
9088
|
+
return [this.contract.populate("withdraw", [
|
|
9089
|
+
uint2564.bnToUint256(shares.toWei()),
|
|
9090
|
+
receiver.address
|
|
9091
|
+
])];
|
|
8963
9092
|
}
|
|
8964
9093
|
rebalanceCall(newBounds, swapParams) {
|
|
8965
9094
|
return [this.contract.populate("rebalance", [
|
|
@@ -8978,22 +9107,121 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
8978
9107
|
handleFeesCall() {
|
|
8979
9108
|
return [this.contract.populate("handle_fees", [])];
|
|
8980
9109
|
}
|
|
8981
|
-
|
|
8982
|
-
|
|
9110
|
+
/**
|
|
9111
|
+
* Calculates assets before and now in a given token of TVL per share to observe growth
|
|
9112
|
+
* @returns {Promise<number>} The weighted average APY across all pools
|
|
9113
|
+
*/
|
|
9114
|
+
async netAPY(blockIdentifier = "pending", sinceBlocks = 2e4) {
|
|
9115
|
+
const tvlNow = await this._getTVL(blockIdentifier);
|
|
9116
|
+
const supplyNow = await this.totalSupply(blockIdentifier);
|
|
9117
|
+
const priceNow = await this.getCurrentPrice(blockIdentifier);
|
|
9118
|
+
let blockNow = typeof blockIdentifier == "number" ? blockIdentifier : (await this.config.provider.getBlockLatestAccepted()).block_number;
|
|
9119
|
+
const blockNowTime = typeof blockIdentifier == "number" ? (await this.config.provider.getBlockWithTxs(blockIdentifier)).timestamp : (/* @__PURE__ */ new Date()).getTime() / 1e3;
|
|
9120
|
+
const blockBefore = blockNow - sinceBlocks;
|
|
9121
|
+
const adjustedSupplyNow = supplyNow.minus(await this.getHarvestRewardShares(blockBefore, blockNow));
|
|
9122
|
+
let blockBeforeInfo = await this.config.provider.getBlockWithTxs(blockBefore);
|
|
9123
|
+
const tvlBefore = await this._getTVL(blockBefore);
|
|
9124
|
+
const supplyBefore = await this.totalSupply(blockBefore);
|
|
9125
|
+
const priceBefore = await this.getCurrentPrice(blockBefore);
|
|
9126
|
+
const tvlInToken0Now = tvlNow.amount0.multipliedBy(priceNow.price).plus(tvlNow.amount1);
|
|
9127
|
+
const tvlPerShareNow = tvlInToken0Now.multipliedBy(1e18).dividedBy(adjustedSupplyNow);
|
|
9128
|
+
const tvlInToken0Bf = tvlBefore.amount0.multipliedBy(priceBefore.price).plus(tvlBefore.amount1);
|
|
9129
|
+
const tvlPerShareBf = tvlInToken0Bf.multipliedBy(1e18).dividedBy(supplyBefore);
|
|
9130
|
+
const timeDiffSeconds = blockNowTime - blockBeforeInfo.timestamp;
|
|
9131
|
+
logger.verbose(`tvlInToken0Now: ${tvlInToken0Now.toString()}`);
|
|
9132
|
+
logger.verbose(`tvlInToken0Bf: ${tvlInToken0Bf.toString()}`);
|
|
9133
|
+
logger.verbose(`tvlPerShareNow: ${tvlPerShareNow.toString()}`);
|
|
9134
|
+
logger.verbose(`tvlPerShareBf: ${tvlPerShareBf.toString()}`);
|
|
9135
|
+
logger.verbose(`Price before: ${priceBefore.price.toString()}`);
|
|
9136
|
+
logger.verbose(`Price now: ${priceNow.price.toString()}`);
|
|
9137
|
+
logger.verbose(`Supply before: ${supplyBefore.toString()}`);
|
|
9138
|
+
logger.verbose(`Supply now: ${adjustedSupplyNow.toString()}`);
|
|
9139
|
+
logger.verbose(`Time diff in seconds: ${timeDiffSeconds}`);
|
|
9140
|
+
const apyForGivenBlocks = Number(tvlPerShareNow.minus(tvlPerShareBf).multipliedBy(1e4).dividedBy(tvlPerShareBf)) / 1e4;
|
|
9141
|
+
return apyForGivenBlocks * (365 * 24 * 3600) / timeDiffSeconds;
|
|
9142
|
+
}
|
|
9143
|
+
async getHarvestRewardShares(fromBlock, toBlock) {
|
|
9144
|
+
const len = Number(await this.contract.call("get_total_rewards"));
|
|
9145
|
+
let shares = Web3Number.fromWei(0, 18);
|
|
9146
|
+
for (let i = len - 1; i > 0; --i) {
|
|
9147
|
+
let record = await this.contract.call("get_rewards_info", [i]);
|
|
9148
|
+
logger.verbose(`${_EkuboCLVault.name}: getHarvestRewardShares: ${i}`);
|
|
9149
|
+
console.log(record);
|
|
9150
|
+
const block = Number(record.block_number);
|
|
9151
|
+
if (block < fromBlock) {
|
|
9152
|
+
return shares;
|
|
9153
|
+
} else if (block > toBlock) {
|
|
9154
|
+
continue;
|
|
9155
|
+
} else {
|
|
9156
|
+
shares = shares.plus(Web3Number.fromWei(record.shares.toString(), 18));
|
|
9157
|
+
}
|
|
9158
|
+
logger.verbose(`${_EkuboCLVault.name}: getHarvestRewardShares: ${i} => ${shares.toWei()}`);
|
|
9159
|
+
}
|
|
9160
|
+
return shares;
|
|
8983
9161
|
}
|
|
8984
|
-
async
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
9162
|
+
async balanceOf(user, blockIdentifier = "pending") {
|
|
9163
|
+
let bal = await this.contract.call("balance_of", [user.address]);
|
|
9164
|
+
return Web3Number.fromWei(bal.toString(), 18);
|
|
9165
|
+
}
|
|
9166
|
+
async getUserTVL(user, blockIdentifier = "pending") {
|
|
9167
|
+
let bal = await this.balanceOf(user, blockIdentifier);
|
|
9168
|
+
const assets = await this.contract.call("convert_to_assets", [uint2564.bnToUint256(bal.toWei())], {
|
|
9169
|
+
blockIdentifier
|
|
9170
|
+
});
|
|
9171
|
+
const poolKey = await this.getPoolKey(blockIdentifier);
|
|
9172
|
+
this.assertValidDepositTokens(poolKey);
|
|
8989
9173
|
const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
|
|
8990
9174
|
const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
|
|
9175
|
+
const amount0 = Web3Number.fromWei(assets.amount0.toString(), token0Info.decimals);
|
|
9176
|
+
const amount1 = Web3Number.fromWei(assets.amount1.toString(), token1Info.decimals);
|
|
8991
9177
|
const P0 = await this.pricer.getPrice(token0Info.symbol);
|
|
8992
9178
|
const P1 = await this.pricer.getPrice(token1Info.symbol);
|
|
8993
9179
|
const token0Usd = Number(amount0.toFixed(13)) * P0.price;
|
|
8994
9180
|
const token1Usd = Number(amount1.toFixed(13)) * P1.price;
|
|
8995
9181
|
return {
|
|
8996
|
-
|
|
9182
|
+
usdValue: token0Usd + token1Usd,
|
|
9183
|
+
token0: {
|
|
9184
|
+
tokenInfo: token0Info,
|
|
9185
|
+
amount: amount0,
|
|
9186
|
+
usdValue: token0Usd
|
|
9187
|
+
},
|
|
9188
|
+
token1: {
|
|
9189
|
+
tokenInfo: token1Info,
|
|
9190
|
+
amount: amount1,
|
|
9191
|
+
usdValue: token1Usd
|
|
9192
|
+
}
|
|
9193
|
+
};
|
|
9194
|
+
}
|
|
9195
|
+
async _getTVL(blockIdentifier = "pending") {
|
|
9196
|
+
const result = await this.contract.call("total_liquidity", [], {
|
|
9197
|
+
blockIdentifier
|
|
9198
|
+
});
|
|
9199
|
+
const bounds = await this.getCurrentBounds(blockIdentifier);
|
|
9200
|
+
const { amount0, amount1 } = await this.getLiquidityToAmounts(Web3Number.fromWei(result.toString(), 18), bounds, blockIdentifier);
|
|
9201
|
+
return { amount0, amount1 };
|
|
9202
|
+
}
|
|
9203
|
+
async totalSupply(blockIdentifier = "pending") {
|
|
9204
|
+
const res = await this.contract.call("total_supply", [], {
|
|
9205
|
+
blockIdentifier
|
|
9206
|
+
});
|
|
9207
|
+
return Web3Number.fromWei(res.toString(), 18);
|
|
9208
|
+
}
|
|
9209
|
+
assertValidDepositTokens(poolKey) {
|
|
9210
|
+
assert(poolKey.token0.eq(this.metadata.depositTokens[0].address), "Expected token0 in depositTokens[0]");
|
|
9211
|
+
assert(poolKey.token1.eq(this.metadata.depositTokens[1].address), "Expected token1 in depositTokens[1]");
|
|
9212
|
+
}
|
|
9213
|
+
async getTVL(blockIdentifier = "pending") {
|
|
9214
|
+
const { amount0, amount1 } = await this._getTVL(blockIdentifier);
|
|
9215
|
+
const poolKey = await this.getPoolKey(blockIdentifier);
|
|
9216
|
+
this.assertValidDepositTokens(poolKey);
|
|
9217
|
+
const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
|
|
9218
|
+
const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
|
|
9219
|
+
const P0 = await this.pricer.getPrice(token0Info.symbol);
|
|
9220
|
+
const P1 = await this.pricer.getPrice(token1Info.symbol);
|
|
9221
|
+
const token0Usd = Number(amount0.toFixed(13)) * P0.price;
|
|
9222
|
+
const token1Usd = Number(amount1.toFixed(13)) * P1.price;
|
|
9223
|
+
return {
|
|
9224
|
+
usdValue: token0Usd + token1Usd,
|
|
8997
9225
|
token0: {
|
|
8998
9226
|
tokenInfo: token0Info,
|
|
8999
9227
|
amount: amount0,
|
|
@@ -9033,7 +9261,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9033
9261
|
const token0Usd = Number(token0Web3.toFixed(13)) * P0.price;
|
|
9034
9262
|
const token1Usd = Number(token1Web3.toFixed(13)) * P1.price;
|
|
9035
9263
|
return {
|
|
9036
|
-
|
|
9264
|
+
usdValue: token0Usd + token1Usd,
|
|
9037
9265
|
token0: {
|
|
9038
9266
|
tokenInfo: token0Info,
|
|
9039
9267
|
amount: token0Web3,
|
|
@@ -9055,11 +9283,11 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9055
9283
|
const truePrice = Number(BigInt(result.toString()) * BigInt(1e9) / BigInt(1e18)) / 1e9;
|
|
9056
9284
|
return truePrice;
|
|
9057
9285
|
}
|
|
9058
|
-
async getCurrentPrice() {
|
|
9059
|
-
const poolKey = await this.getPoolKey();
|
|
9060
|
-
return this._getCurrentPrice(poolKey);
|
|
9286
|
+
async getCurrentPrice(blockIdentifier = "pending") {
|
|
9287
|
+
const poolKey = await this.getPoolKey(blockIdentifier);
|
|
9288
|
+
return this._getCurrentPrice(poolKey, blockIdentifier);
|
|
9061
9289
|
}
|
|
9062
|
-
async _getCurrentPrice(poolKey) {
|
|
9290
|
+
async _getCurrentPrice(poolKey, blockIdentifier = "pending") {
|
|
9063
9291
|
const priceInfo = await this.ekuboPositionsContract.call("get_pool_price", [
|
|
9064
9292
|
{
|
|
9065
9293
|
token0: poolKey.token0.address,
|
|
@@ -9068,35 +9296,44 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9068
9296
|
tick_spacing: poolKey.tick_spacing,
|
|
9069
9297
|
extension: poolKey.extension
|
|
9070
9298
|
}
|
|
9071
|
-
]
|
|
9299
|
+
], {
|
|
9300
|
+
blockIdentifier
|
|
9301
|
+
});
|
|
9072
9302
|
const sqrtRatio = _EkuboCLVault.div2Power128(BigInt(priceInfo.sqrt_ratio.toString()));
|
|
9303
|
+
console.log(`EkuboCLVault: getCurrentPrice: blockIdentifier: ${blockIdentifier}, sqrtRatio: ${sqrtRatio}, ${priceInfo.sqrt_ratio.toString()}`);
|
|
9073
9304
|
const price = sqrtRatio * sqrtRatio;
|
|
9074
9305
|
const tick = _EkuboCLVault.priceToTick(price, true, Number(poolKey.tick_spacing));
|
|
9306
|
+
console.log(`EkuboCLVault: getCurrentPrice: blockIdentifier: ${blockIdentifier}, price: ${price}, tick: ${tick.mag}, ${tick.sign}`);
|
|
9075
9307
|
return {
|
|
9076
9308
|
price,
|
|
9077
|
-
tick: tick.mag * (tick.sign == 0 ? 1 : -1)
|
|
9309
|
+
tick: tick.mag * (tick.sign == 0 ? 1 : -1),
|
|
9310
|
+
sqrtRatio: priceInfo.sqrt_ratio.toString()
|
|
9078
9311
|
};
|
|
9079
9312
|
}
|
|
9080
|
-
async getCurrentBounds() {
|
|
9081
|
-
const result = await this.contract.call("get_position_key", []
|
|
9313
|
+
async getCurrentBounds(blockIdentifier = "pending") {
|
|
9314
|
+
const result = await this.contract.call("get_position_key", [], {
|
|
9315
|
+
blockIdentifier
|
|
9316
|
+
});
|
|
9082
9317
|
return {
|
|
9083
9318
|
lowerTick: _EkuboCLVault.i129ToNumber(result.bounds.lower),
|
|
9084
9319
|
upperTick: _EkuboCLVault.i129ToNumber(result.bounds.upper)
|
|
9085
9320
|
};
|
|
9086
9321
|
}
|
|
9087
|
-
static div2Power128(
|
|
9088
|
-
return Number(BigInt(
|
|
9322
|
+
static div2Power128(num4) {
|
|
9323
|
+
return Number(BigInt(num4.toString()) * 1000000n / BigInt(2 ** 128)) / 1e6;
|
|
9089
9324
|
}
|
|
9090
9325
|
static priceToTick(price, isRoundDown, tickSpacing) {
|
|
9091
9326
|
const value = isRoundDown ? Math.floor(Math.log(price) / Math.log(1.000001)) : Math.ceil(Math.log(price) / Math.log(1.000001));
|
|
9092
9327
|
const tick = Math.floor(value / tickSpacing) * tickSpacing;
|
|
9093
9328
|
return this.tickToi129(tick);
|
|
9094
9329
|
}
|
|
9095
|
-
async getPoolKey() {
|
|
9330
|
+
async getPoolKey(blockIdentifier = "pending") {
|
|
9096
9331
|
if (this.poolKey) {
|
|
9097
9332
|
return this.poolKey;
|
|
9098
9333
|
}
|
|
9099
|
-
const result = await this.contract.call("get_settings", []
|
|
9334
|
+
const result = await this.contract.call("get_settings", [], {
|
|
9335
|
+
blockIdentifier
|
|
9336
|
+
});
|
|
9100
9337
|
const poolKey = {
|
|
9101
9338
|
token0: ContractAddr.from(result.pool_key.token0.toString()),
|
|
9102
9339
|
token1: ContractAddr.from(result.pool_key.token1.toString()),
|
|
@@ -9127,25 +9364,24 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9127
9364
|
* @param amount1: amount of token1
|
|
9128
9365
|
* @returns {amount0, amount1}
|
|
9129
9366
|
*/
|
|
9130
|
-
async _getExpectedAmountsForLiquidity(amount0, amount1, bounds) {
|
|
9367
|
+
async _getExpectedAmountsForLiquidity(amount0, amount1, bounds, justUseInputAmount = true) {
|
|
9131
9368
|
assert(amount0.greaterThan(0) || amount1.greaterThan(0), "Amount is 0");
|
|
9132
|
-
const
|
|
9133
|
-
const sampleLiq = 1e18;
|
|
9369
|
+
const sampleLiq = 1e20;
|
|
9134
9370
|
const { amount0: sampleAmount0, amount1: sampleAmount1 } = await this.getLiquidityToAmounts(Web3Number.fromWei(sampleLiq.toString(), 18), bounds);
|
|
9135
9371
|
logger.verbose(`${_EkuboCLVault.name}: _getExpectedAmountsForLiquidity => sampleAmount0: ${sampleAmount0.toString()}, sampleAmount1: ${sampleAmount1.toString()}`);
|
|
9136
|
-
assert(!sampleAmount0.eq(0)
|
|
9372
|
+
assert(!sampleAmount0.eq(0) || !sampleAmount1.eq(0), "Sample amount is 0");
|
|
9137
9373
|
const price = await (await this.getCurrentPrice()).price;
|
|
9138
9374
|
logger.verbose(`${_EkuboCLVault.name}: _getExpectedAmountsForLiquidity => price: ${price}`);
|
|
9139
9375
|
if (amount1.eq(0) && amount0.greaterThan(0)) {
|
|
9140
9376
|
if (sampleAmount1.eq(0)) {
|
|
9141
9377
|
return {
|
|
9142
9378
|
amount0,
|
|
9143
|
-
amount1: Web3Number.fromWei("0",
|
|
9379
|
+
amount1: Web3Number.fromWei("0", amount1.decimals),
|
|
9144
9380
|
ratio: Infinity
|
|
9145
9381
|
};
|
|
9146
9382
|
} else if (sampleAmount0.eq(0)) {
|
|
9147
9383
|
return {
|
|
9148
|
-
amount0: Web3Number.fromWei("0",
|
|
9384
|
+
amount0: Web3Number.fromWei("0", amount0.decimals),
|
|
9149
9385
|
amount1: amount0.multipliedBy(price),
|
|
9150
9386
|
ratio: 0
|
|
9151
9387
|
};
|
|
@@ -9153,21 +9389,41 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9153
9389
|
} else if (amount0.eq(0) && amount1.greaterThan(0)) {
|
|
9154
9390
|
if (sampleAmount0.eq(0)) {
|
|
9155
9391
|
return {
|
|
9156
|
-
amount0: Web3Number.fromWei("0",
|
|
9392
|
+
amount0: Web3Number.fromWei("0", amount0.decimals),
|
|
9157
9393
|
amount1,
|
|
9158
9394
|
ratio: 0
|
|
9159
9395
|
};
|
|
9160
9396
|
} else if (sampleAmount1.eq(0)) {
|
|
9161
9397
|
return {
|
|
9162
9398
|
amount0: amount1.dividedBy(price),
|
|
9163
|
-
amount1: Web3Number.fromWei("0",
|
|
9399
|
+
amount1: Web3Number.fromWei("0", amount1.decimals),
|
|
9164
9400
|
ratio: Infinity
|
|
9165
9401
|
};
|
|
9166
9402
|
}
|
|
9167
9403
|
}
|
|
9168
|
-
|
|
9404
|
+
assert(sampleAmount0.decimals == sampleAmount1.decimals, "Sample amounts have different decimals");
|
|
9405
|
+
const ratioWeb3Number = sampleAmount0.multipliedBy(1e18).dividedBy(sampleAmount1.toString()).dividedBy(1e18);
|
|
9406
|
+
const ratio = Number(ratioWeb3Number.toFixed(18));
|
|
9169
9407
|
logger.verbose(`${_EkuboCLVault.name}: ${this.metadata.name} => ratio: ${ratio.toString()}`);
|
|
9170
|
-
|
|
9408
|
+
if (justUseInputAmount)
|
|
9409
|
+
return this._solveExpectedAmountsEq(amount0, amount1, ratioWeb3Number, price);
|
|
9410
|
+
if (amount1.eq(0) && amount0.greaterThan(0)) {
|
|
9411
|
+
const _amount1 = amount0.dividedBy(ratioWeb3Number);
|
|
9412
|
+
return {
|
|
9413
|
+
amount0,
|
|
9414
|
+
amount1: _amount1,
|
|
9415
|
+
ratio
|
|
9416
|
+
};
|
|
9417
|
+
} else if (amount0.eq(0) && amount1.greaterThan(0)) {
|
|
9418
|
+
const _amount0 = amount1.multipliedBy(ratio);
|
|
9419
|
+
return {
|
|
9420
|
+
amount0: _amount0,
|
|
9421
|
+
amount1,
|
|
9422
|
+
ratio
|
|
9423
|
+
};
|
|
9424
|
+
} else {
|
|
9425
|
+
throw new Error("Both amounts are non-zero, cannot compute expected amounts");
|
|
9426
|
+
}
|
|
9171
9427
|
}
|
|
9172
9428
|
_solveExpectedAmountsEq(availableAmount0, availableAmount1, ratio, price) {
|
|
9173
9429
|
const y = ratio.multipliedBy(availableAmount1).minus(availableAmount0).dividedBy(ratio.plus(1 / price));
|
|
@@ -9181,10 +9437,11 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9181
9437
|
async getSwapInfoToHandleUnused(considerRebalance = true) {
|
|
9182
9438
|
const poolKey = await this.getPoolKey();
|
|
9183
9439
|
const erc20Mod = new ERC20(this.config);
|
|
9184
|
-
const token0Bal1 = await erc20Mod.balanceOf(poolKey.token0, this.address.address, 18);
|
|
9185
|
-
const token1Bal1 = await erc20Mod.balanceOf(poolKey.token1, this.address.address, 18);
|
|
9186
9440
|
const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
|
|
9187
9441
|
const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
|
|
9442
|
+
const token0Bal1 = await erc20Mod.balanceOf(poolKey.token0, this.address.address, token0Info.decimals);
|
|
9443
|
+
const token1Bal1 = await erc20Mod.balanceOf(poolKey.token1, this.address.address, token1Info.decimals);
|
|
9444
|
+
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => token0Bal1: ${token0Bal1.toString()}, token1Bal1: ${token1Bal1.toString()}`);
|
|
9188
9445
|
const token0Price = await this.pricer.getPrice(token0Info.symbol);
|
|
9189
9446
|
const token1Price = await this.pricer.getPrice(token1Info.symbol);
|
|
9190
9447
|
const token0PriceUsd = token0Price.price * Number(token0Bal1.toFixed(13));
|
|
@@ -9195,6 +9452,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9195
9452
|
let token0Bal = token0Bal1;
|
|
9196
9453
|
let token1Bal = token1Bal1;
|
|
9197
9454
|
if (considerRebalance) {
|
|
9455
|
+
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => considerRebalance: true`);
|
|
9198
9456
|
const tvl = await this.getTVL();
|
|
9199
9457
|
token0Bal = token0Bal.plus(tvl.token0.amount.toString());
|
|
9200
9458
|
token1Bal = token1Bal.plus(tvl.token1.amount.toString());
|
|
@@ -9204,7 +9462,10 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9204
9462
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => token0Bal: ${token0Bal.toString()}, token1Bal: ${token1Bal.toString()}`);
|
|
9205
9463
|
const newBounds = await this.getNewBounds();
|
|
9206
9464
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => newBounds: ${newBounds.lowerTick}, ${newBounds.upperTick}`);
|
|
9207
|
-
|
|
9465
|
+
return await this.getSwapInfoGivenAmounts(poolKey, token0Bal, token1Bal, newBounds);
|
|
9466
|
+
}
|
|
9467
|
+
async getSwapInfoGivenAmounts(poolKey, token0Bal, token1Bal, bounds) {
|
|
9468
|
+
let expectedAmounts = await this._getExpectedAmountsForLiquidity(token0Bal, token1Bal, bounds);
|
|
9208
9469
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => expectedAmounts: ${expectedAmounts.amount0.toString()}, ${expectedAmounts.amount1.toString()}`);
|
|
9209
9470
|
let retry = 0;
|
|
9210
9471
|
const maxRetry = 10;
|
|
@@ -9225,6 +9486,19 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9225
9486
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => tokenToSell: ${tokenToSell.address}, tokenToBuy: ${tokenToBuy.address}, amountToSell: ${amountToSell.toWei()}`);
|
|
9226
9487
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => remainingSellAmount: ${remainingSellAmount.toString()}`);
|
|
9227
9488
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => expectedRatio: ${expectedRatio}`);
|
|
9489
|
+
if (amountToSell.eq(0)) {
|
|
9490
|
+
return {
|
|
9491
|
+
token_from_address: tokenToSell.address,
|
|
9492
|
+
token_from_amount: uint2564.bnToUint256(0),
|
|
9493
|
+
token_to_address: tokenToSell.address,
|
|
9494
|
+
token_to_amount: uint2564.bnToUint256(0),
|
|
9495
|
+
token_to_min_amount: uint2564.bnToUint256(0),
|
|
9496
|
+
beneficiary: this.address.address,
|
|
9497
|
+
integrator_fee_amount_bps: 0,
|
|
9498
|
+
integrator_fee_recipient: this.address.address,
|
|
9499
|
+
routes: []
|
|
9500
|
+
};
|
|
9501
|
+
}
|
|
9228
9502
|
const quote = await this.avnu.getQuotes(tokenToSell.address, tokenToBuy.address, amountToSell.toWei(), this.address.address);
|
|
9229
9503
|
if (remainingSellAmount.eq(0)) {
|
|
9230
9504
|
const minAmountOut = Web3Number.fromWei(quote.buyAmount.toString(), tokenToBuyInfo.decimals).multipliedBy(0.9999);
|
|
@@ -9247,6 +9521,73 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9247
9521
|
}
|
|
9248
9522
|
throw new Error("Failed to get swap info");
|
|
9249
9523
|
}
|
|
9524
|
+
/**
|
|
9525
|
+
* Attempts to rebalance the vault by iteratively adjusting swap amounts if initial attempt fails.
|
|
9526
|
+
* Uses binary search approach to find optimal swap amount.
|
|
9527
|
+
*
|
|
9528
|
+
* @param newBounds - The new tick bounds to rebalance to
|
|
9529
|
+
* @param swapInfo - Initial swap parameters for rebalancing
|
|
9530
|
+
* @param acc - Account to estimate gas fees with
|
|
9531
|
+
* @param retry - Current retry attempt number (default 0)
|
|
9532
|
+
* @param adjustmentFactor - Percentage to adjust swap amount by (default 1)
|
|
9533
|
+
* @param isToken0Deficit - Whether token0 balance needs increasing (default true)
|
|
9534
|
+
* @returns Array of contract calls needed for rebalancing
|
|
9535
|
+
* @throws Error if max retries reached without successful rebalance
|
|
9536
|
+
*/
|
|
9537
|
+
async rebalanceIter(swapInfo, acc, estimateCall, retry = 0, adjustmentFactor = 1, isToken0Deficit = true) {
|
|
9538
|
+
const MAX_RETRIES = 20;
|
|
9539
|
+
const MIN_ADJUSTMENT = 1e-3;
|
|
9540
|
+
logger.verbose(
|
|
9541
|
+
`Rebalancing ${this.metadata.name}: retry=${retry}, adjustment=${adjustmentFactor}%, token0Deficit=${isToken0Deficit}`
|
|
9542
|
+
);
|
|
9543
|
+
const fromAmount = uint2564.uint256ToBN(swapInfo.token_from_amount);
|
|
9544
|
+
logger.verbose(
|
|
9545
|
+
`Selling ${fromAmount.toString()} of token ${swapInfo.token_from_address}`
|
|
9546
|
+
);
|
|
9547
|
+
try {
|
|
9548
|
+
const calls = await estimateCall(swapInfo);
|
|
9549
|
+
await acc.estimateInvokeFee(calls);
|
|
9550
|
+
return calls;
|
|
9551
|
+
} catch (err) {
|
|
9552
|
+
if (retry >= MAX_RETRIES) {
|
|
9553
|
+
logger.error(`Rebalance failed after ${MAX_RETRIES} retries`);
|
|
9554
|
+
throw err;
|
|
9555
|
+
}
|
|
9556
|
+
if (adjustmentFactor < MIN_ADJUSTMENT) {
|
|
9557
|
+
logger.error("Adjustment factor too small, likely oscillating");
|
|
9558
|
+
throw new Error("Failed to converge on valid swap amount");
|
|
9559
|
+
}
|
|
9560
|
+
logger.error(`Rebalance attempt ${retry + 1} failed, adjusting swap amount...`);
|
|
9561
|
+
const newSwapInfo = { ...swapInfo };
|
|
9562
|
+
const currentAmount = Web3Number.fromWei(fromAmount.toString(), 18);
|
|
9563
|
+
if (err.message.includes("invalid token0 balance") || err.message.includes("invalid token0 amount")) {
|
|
9564
|
+
logger.verbose("Reducing swap amount - excess token0");
|
|
9565
|
+
newSwapInfo.token_from_amount = uint2564.bnToUint256(
|
|
9566
|
+
currentAmount.multipliedBy((100 - adjustmentFactor) / 100).toWei()
|
|
9567
|
+
);
|
|
9568
|
+
adjustmentFactor = isToken0Deficit ? adjustmentFactor * 2 : adjustmentFactor / 2;
|
|
9569
|
+
isToken0Deficit = true;
|
|
9570
|
+
} else if (err.message.includes("invalid token1 balance") || err.message.includes("invalid token1 amount")) {
|
|
9571
|
+
logger.verbose("Increasing swap amount - excess token1");
|
|
9572
|
+
newSwapInfo.token_from_amount = uint2564.bnToUint256(
|
|
9573
|
+
currentAmount.multipliedBy((100 + adjustmentFactor) / 100).toWei()
|
|
9574
|
+
);
|
|
9575
|
+
adjustmentFactor = isToken0Deficit ? adjustmentFactor / 2 : adjustmentFactor * 2;
|
|
9576
|
+
isToken0Deficit = false;
|
|
9577
|
+
} else {
|
|
9578
|
+
logger.error("Unexpected error:", err);
|
|
9579
|
+
}
|
|
9580
|
+
newSwapInfo.token_to_min_amount = uint2564.bnToUint256("0");
|
|
9581
|
+
return this.rebalanceIter(
|
|
9582
|
+
newSwapInfo,
|
|
9583
|
+
acc,
|
|
9584
|
+
estimateCall,
|
|
9585
|
+
retry + 1,
|
|
9586
|
+
adjustmentFactor,
|
|
9587
|
+
isToken0Deficit
|
|
9588
|
+
);
|
|
9589
|
+
}
|
|
9590
|
+
}
|
|
9250
9591
|
static tickToi129(tick) {
|
|
9251
9592
|
if (tick < 0) {
|
|
9252
9593
|
return {
|
|
@@ -9269,21 +9610,23 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9269
9610
|
static tickToPrice(tick) {
|
|
9270
9611
|
return Math.pow(1.000001, Number(tick));
|
|
9271
9612
|
}
|
|
9272
|
-
async getLiquidityToAmounts(liquidity, bounds) {
|
|
9273
|
-
const currentPrice = await this.getCurrentPrice();
|
|
9274
|
-
const lowerPrice =
|
|
9275
|
-
const upperPrice =
|
|
9613
|
+
async getLiquidityToAmounts(liquidity, bounds, blockIdentifier = "pending", _poolKey = null, _currentPrice = null) {
|
|
9614
|
+
const currentPrice = _currentPrice || await this.getCurrentPrice(blockIdentifier);
|
|
9615
|
+
const lowerPrice = _EkuboCLVault.tickToPrice(bounds.lowerTick);
|
|
9616
|
+
const upperPrice = _EkuboCLVault.tickToPrice(bounds.upperTick);
|
|
9276
9617
|
logger.verbose(`${_EkuboCLVault.name}: getLiquidityToAmounts => currentPrice: ${currentPrice.price}, lowerPrice: ${lowerPrice}, upperPrice: ${upperPrice}`);
|
|
9277
9618
|
const result = await this.ekuboMathContract.call("liquidity_delta_to_amount_delta", [
|
|
9278
|
-
uint2564.bnToUint256(
|
|
9619
|
+
uint2564.bnToUint256(currentPrice.sqrtRatio),
|
|
9279
9620
|
{
|
|
9280
9621
|
mag: liquidity.toWei(),
|
|
9281
9622
|
sign: 0
|
|
9282
9623
|
},
|
|
9283
9624
|
uint2564.bnToUint256(_EkuboCLVault.priceToSqrtRatio(lowerPrice).toString()),
|
|
9284
9625
|
uint2564.bnToUint256(_EkuboCLVault.priceToSqrtRatio(upperPrice).toString())
|
|
9285
|
-
]
|
|
9286
|
-
|
|
9626
|
+
], {
|
|
9627
|
+
blockIdentifier
|
|
9628
|
+
});
|
|
9629
|
+
const poolKey = _poolKey || await this.getPoolKey(blockIdentifier);
|
|
9287
9630
|
const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
|
|
9288
9631
|
const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
|
|
9289
9632
|
const amount0 = Web3Number.fromWei(_EkuboCLVault.i129ToNumber(result.amount0).toString(), token0Info.decimals);
|
|
@@ -9293,32 +9636,110 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9293
9636
|
amount1
|
|
9294
9637
|
};
|
|
9295
9638
|
}
|
|
9639
|
+
async harvest(acc) {
|
|
9640
|
+
const ekuboHarvests = new EkuboHarvests(this.config);
|
|
9641
|
+
const unClaimedRewards = await ekuboHarvests.getUnHarvestedRewards(this.address);
|
|
9642
|
+
const poolKey = await this.getPoolKey();
|
|
9643
|
+
const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
|
|
9644
|
+
const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
|
|
9645
|
+
const bounds = await this.getCurrentBounds();
|
|
9646
|
+
const calls = [];
|
|
9647
|
+
for (let claim of unClaimedRewards) {
|
|
9648
|
+
const fee = claim.claim.amount.multipliedBy(this.metadata.additionalInfo.feeBps).dividedBy(1e4);
|
|
9649
|
+
const postFeeAmount = claim.claim.amount.minus(fee);
|
|
9650
|
+
const isToken1 = claim.token.eq(poolKey.token1);
|
|
9651
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => Processing claim, isToken1: ${isToken1} amount: ${postFeeAmount.toWei()}`);
|
|
9652
|
+
const token0Amt = isToken1 ? new Web3Number(0, token0Info.decimals) : postFeeAmount;
|
|
9653
|
+
const token1Amt = isToken1 ? postFeeAmount : new Web3Number(0, token0Info.decimals);
|
|
9654
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => token0Amt: ${token0Amt.toString()}, token1Amt: ${token1Amt.toString()}`);
|
|
9655
|
+
const swapInfo = await this.getSwapInfoGivenAmounts(poolKey, token0Amt, token1Amt, bounds);
|
|
9656
|
+
swapInfo.token_to_address = token0Info.address.address;
|
|
9657
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => swapInfo: ${JSON.stringify(swapInfo)}`);
|
|
9658
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => claim: ${JSON.stringify(claim)}`);
|
|
9659
|
+
const harvestEstimateCall = async (swapInfo1) => {
|
|
9660
|
+
const swap1Amount = Web3Number.fromWei(uint2564.uint256ToBN(swapInfo1.token_from_amount).toString(), 18);
|
|
9661
|
+
const remainingAmount = postFeeAmount.minus(swap1Amount);
|
|
9662
|
+
const swapInfo2 = { ...swapInfo, token_from_amount: uint2564.bnToUint256(remainingAmount.toWei()) };
|
|
9663
|
+
swapInfo2.token_to_address = token1Info.address.address;
|
|
9664
|
+
const calldata = [
|
|
9665
|
+
claim.rewardsContract.address,
|
|
9666
|
+
{
|
|
9667
|
+
id: claim.claim.id,
|
|
9668
|
+
amount: claim.claim.amount.toWei(),
|
|
9669
|
+
claimee: claim.claim.claimee.address
|
|
9670
|
+
},
|
|
9671
|
+
claim.proof.map((p) => num3.getDecimalString(p)),
|
|
9672
|
+
swapInfo,
|
|
9673
|
+
swapInfo2
|
|
9674
|
+
];
|
|
9675
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => calldata: ${JSON.stringify(calldata)}`);
|
|
9676
|
+
return [this.contract.populate("harvest", calldata)];
|
|
9677
|
+
};
|
|
9678
|
+
const _callsFinal = await this.rebalanceIter(swapInfo, acc, harvestEstimateCall);
|
|
9679
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => _callsFinal: ${JSON.stringify(_callsFinal)}`);
|
|
9680
|
+
calls.push(..._callsFinal);
|
|
9681
|
+
}
|
|
9682
|
+
return calls;
|
|
9683
|
+
}
|
|
9684
|
+
async getInvestmentFlows() {
|
|
9685
|
+
const netYield = await this.netAPY();
|
|
9686
|
+
const poolKey = await this.getPoolKey();
|
|
9687
|
+
const linkedFlow = {
|
|
9688
|
+
title: this.metadata.name,
|
|
9689
|
+
subItems: [{ key: "Pool", value: `${(_EkuboCLVault.div2Power128(BigInt(poolKey.fee)) * 100).toFixed(2)}%, ${poolKey.tick_spacing} tick spacing` }],
|
|
9690
|
+
linkedFlows: [],
|
|
9691
|
+
style: { backgroundColor: "#35484f" /* Blue */.valueOf() }
|
|
9692
|
+
};
|
|
9693
|
+
const baseFlow = {
|
|
9694
|
+
id: "base",
|
|
9695
|
+
title: "Your Deposit",
|
|
9696
|
+
subItems: [{ key: `Net yield`, value: `${(netYield * 100).toFixed(2)}%` }, { key: `Performance Fee`, value: `${(this.metadata.additionalInfo.feeBps / 100).toFixed(2)}%` }],
|
|
9697
|
+
linkedFlows: [linkedFlow],
|
|
9698
|
+
style: { backgroundColor: "#6e53dc" /* Purple */.valueOf() }
|
|
9699
|
+
};
|
|
9700
|
+
const rebalanceFlow = {
|
|
9701
|
+
id: "rebalance",
|
|
9702
|
+
title: "Automated Rebalance",
|
|
9703
|
+
subItems: [{
|
|
9704
|
+
key: "Range selection",
|
|
9705
|
+
value: `${this.metadata.additionalInfo.newBounds.lower * Number(poolKey.tick_spacing)} to ${this.metadata.additionalInfo.newBounds.upper * Number(poolKey.tick_spacing)} ticks`
|
|
9706
|
+
}],
|
|
9707
|
+
linkedFlows: [linkedFlow],
|
|
9708
|
+
style: { backgroundColor: "purple" /* Green */.valueOf() }
|
|
9709
|
+
};
|
|
9710
|
+
return [baseFlow, rebalanceFlow];
|
|
9711
|
+
}
|
|
9296
9712
|
};
|
|
9297
|
-
var _description2 = "
|
|
9713
|
+
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.";
|
|
9298
9714
|
var _protocol2 = { name: "Ekubo", logo: "https://app.ekubo.org/favicon.ico" };
|
|
9299
9715
|
var _riskFactor2 = [
|
|
9300
9716
|
{ type: "Smart Contract Risk" /* SMART_CONTRACT_RISK */, value: 0.5, weight: 25 },
|
|
9301
9717
|
{ type: "Impermanent Loss Risk" /* IMPERMANENT_LOSS */, value: 1, weight: 75 }
|
|
9302
9718
|
];
|
|
9719
|
+
var AUDIT_URL2 = "https://assets.strkfarm.com/strkfarm/audit_report_vesu_and_ekubo_strats.pdf";
|
|
9303
9720
|
var EkuboCLVaultStrategies = [{
|
|
9304
9721
|
name: "Ekubo xSTRK/STRK",
|
|
9305
|
-
description: _description2,
|
|
9722
|
+
description: _description2.replace("{{POOL_NAME}}", "xSTRK/STRK"),
|
|
9306
9723
|
address: ContractAddr.from("0x01f083b98674bc21effee29ef443a00c7b9a500fd92cf30341a3da12c73f2324"),
|
|
9307
9724
|
type: "Other",
|
|
9308
|
-
|
|
9725
|
+
// must be same order as poolKey token0 and token1
|
|
9726
|
+
depositTokens: [Global.getDefaultTokens().find((t) => t.symbol === "xSTRK"), Global.getDefaultTokens().find((t) => t.symbol === "STRK")],
|
|
9309
9727
|
protocols: [_protocol2],
|
|
9728
|
+
auditUrl: AUDIT_URL2,
|
|
9310
9729
|
maxTVL: Web3Number.fromWei("0", 18),
|
|
9311
9730
|
risk: {
|
|
9312
9731
|
riskFactor: _riskFactor2,
|
|
9313
9732
|
netRisk: _riskFactor2.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / _riskFactor2.reduce((acc, curr) => acc + curr.weight, 0),
|
|
9314
9733
|
notARisks: getNoRiskTags(_riskFactor2)
|
|
9315
9734
|
},
|
|
9735
|
+
apyMethodology: "APY based on 7-day historical performance, including fees and rewards.",
|
|
9316
9736
|
additionalInfo: {
|
|
9317
9737
|
newBounds: {
|
|
9318
9738
|
lower: -1,
|
|
9319
9739
|
upper: 1
|
|
9320
9740
|
},
|
|
9321
|
-
lstContract: ContractAddr.from("0x028d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a")
|
|
9741
|
+
lstContract: ContractAddr.from("0x028d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a"),
|
|
9742
|
+
feeBps: 1e3
|
|
9322
9743
|
}
|
|
9323
9744
|
}];
|
|
9324
9745
|
|
|
@@ -9428,7 +9849,7 @@ var PricerRedis = class extends Pricer {
|
|
|
9428
9849
|
|
|
9429
9850
|
// src/utils/store.ts
|
|
9430
9851
|
import fs, { readFileSync, writeFileSync } from "fs";
|
|
9431
|
-
import { Account, constants } from "starknet";
|
|
9852
|
+
import { Account as Account2, constants } from "starknet";
|
|
9432
9853
|
import * as crypto2 from "crypto";
|
|
9433
9854
|
|
|
9434
9855
|
// src/utils/encrypt.ts
|
|
@@ -9522,7 +9943,7 @@ var Store = class _Store {
|
|
|
9522
9943
|
}
|
|
9523
9944
|
logger.verbose(`Account loaded: ${accountKey} from network: ${this.config.network}`);
|
|
9524
9945
|
logger.verbose(`Address: ${data.address}`);
|
|
9525
|
-
const acc = new
|
|
9946
|
+
const acc = new Account2(this.config.provider, data.address, data.pk, void 0, txVersion);
|
|
9526
9947
|
return acc;
|
|
9527
9948
|
}
|
|
9528
9949
|
addAccount(accountKey, address, pk) {
|
|
@@ -9586,6 +10007,7 @@ var Store = class _Store {
|
|
|
9586
10007
|
export {
|
|
9587
10008
|
AutoCompounderSTRK,
|
|
9588
10009
|
AvnuWrapper,
|
|
10010
|
+
BaseStrategy,
|
|
9589
10011
|
ContractAddr,
|
|
9590
10012
|
ERC20,
|
|
9591
10013
|
EkuboCLVault,
|