@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.browser.mjs
CHANGED
|
@@ -15,19 +15,19 @@ var _Web3Number = class extends BigNumber {
|
|
|
15
15
|
return this.mul(10 ** this.decimals).toFixed(0);
|
|
16
16
|
}
|
|
17
17
|
multipliedBy(value) {
|
|
18
|
-
|
|
18
|
+
const _value = this.getStandardString(value);
|
|
19
19
|
return this.construct(this.mul(_value).toString(), this.decimals);
|
|
20
20
|
}
|
|
21
21
|
dividedBy(value) {
|
|
22
|
-
|
|
22
|
+
const _value = this.getStandardString(value);
|
|
23
23
|
return this.construct(this.div(_value).toString(), this.decimals);
|
|
24
24
|
}
|
|
25
25
|
plus(value) {
|
|
26
|
-
const _value =
|
|
26
|
+
const _value = this.getStandardString(value);
|
|
27
27
|
return this.construct(this.add(_value).toString(), this.decimals);
|
|
28
28
|
}
|
|
29
29
|
minus(n, base) {
|
|
30
|
-
const _value =
|
|
30
|
+
const _value = this.getStandardString(n);
|
|
31
31
|
return this.construct(super.minus(_value, base).toString(), this.decimals);
|
|
32
32
|
}
|
|
33
33
|
construct(value, decimals) {
|
|
@@ -43,11 +43,17 @@ var _Web3Number = class extends BigNumber {
|
|
|
43
43
|
return this.toString();
|
|
44
44
|
}
|
|
45
45
|
maxToFixedDecimals() {
|
|
46
|
-
return Math.min(this.decimals,
|
|
46
|
+
return Math.min(this.decimals, 18);
|
|
47
|
+
}
|
|
48
|
+
getStandardString(value) {
|
|
49
|
+
if (typeof value == "string") {
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
return value.toFixed(this.maxToFixedDecimals());
|
|
47
53
|
}
|
|
48
54
|
};
|
|
49
|
-
BigNumber.config({ DECIMAL_PLACES: 18 });
|
|
50
|
-
_Web3Number.config({ DECIMAL_PLACES: 18 });
|
|
55
|
+
BigNumber.config({ DECIMAL_PLACES: 18, ROUNDING_MODE: BigNumber.ROUND_DOWN });
|
|
56
|
+
_Web3Number.config({ DECIMAL_PLACES: 18, ROUNDING_MODE: BigNumber.ROUND_DOWN });
|
|
51
57
|
|
|
52
58
|
// src/dataTypes/bignumber.browser.ts
|
|
53
59
|
var Web3Number = class _Web3Number2 extends _Web3Number {
|
|
@@ -110,42 +116,48 @@ var defaultTokens = [{
|
|
|
110
116
|
logo: "https://assets.coingecko.com/coins/images/26433/small/starknet.png",
|
|
111
117
|
address: ContractAddr.from("0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d"),
|
|
112
118
|
decimals: 18,
|
|
113
|
-
coingeckId: "starknet"
|
|
119
|
+
coingeckId: "starknet",
|
|
120
|
+
displayDecimals: 2
|
|
114
121
|
}, {
|
|
115
122
|
name: "xSTRK",
|
|
116
123
|
symbol: "xSTRK",
|
|
117
124
|
logo: "https://dashboard.endur.fi/endur-fi.svg",
|
|
118
125
|
address: ContractAddr.from("0x028d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a"),
|
|
119
126
|
decimals: 18,
|
|
120
|
-
coingeckId: void 0
|
|
127
|
+
coingeckId: void 0,
|
|
128
|
+
displayDecimals: 2
|
|
121
129
|
}, {
|
|
122
130
|
name: "ETH",
|
|
123
131
|
symbol: "ETH",
|
|
124
132
|
logo: "https://opbnb.bscscan.com/token/images/ether.svg",
|
|
125
133
|
address: ContractAddr.from("0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"),
|
|
126
134
|
decimals: 18,
|
|
127
|
-
coingeckId: void 0
|
|
135
|
+
coingeckId: void 0,
|
|
136
|
+
displayDecimals: 4
|
|
128
137
|
}, {
|
|
129
138
|
name: "USDC",
|
|
130
139
|
symbol: "USDC",
|
|
131
140
|
logo: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
|
|
132
141
|
address: ContractAddr.from("0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"),
|
|
133
142
|
decimals: 6,
|
|
134
|
-
coingeckId: void 0
|
|
143
|
+
coingeckId: void 0,
|
|
144
|
+
displayDecimals: 2
|
|
135
145
|
}, {
|
|
136
146
|
name: "USDT",
|
|
137
147
|
symbol: "USDT",
|
|
138
148
|
logo: "https://assets.coingecko.com/coins/images/325/small/Tether.png",
|
|
139
149
|
address: ContractAddr.from("0x68f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"),
|
|
140
150
|
decimals: 6,
|
|
141
|
-
coingeckId: void 0
|
|
151
|
+
coingeckId: void 0,
|
|
152
|
+
displayDecimals: 2
|
|
142
153
|
}, {
|
|
143
154
|
name: "WBTC",
|
|
144
155
|
symbol: "WBTC",
|
|
145
156
|
logo: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599/logo.png",
|
|
146
157
|
address: ContractAddr.from("0x3fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac"),
|
|
147
158
|
decimals: 8,
|
|
148
|
-
coingeckId: void 0
|
|
159
|
+
coingeckId: void 0,
|
|
160
|
+
displayDecimals: 6
|
|
149
161
|
}];
|
|
150
162
|
var tokens = defaultTokens;
|
|
151
163
|
var Global = class _Global {
|
|
@@ -177,7 +189,8 @@ var Global = class _Global {
|
|
|
177
189
|
address: ContractAddr.from(token.address),
|
|
178
190
|
decimals: token.decimals,
|
|
179
191
|
logo: token.logoUri,
|
|
180
|
-
coingeckId: token.extensions.coingeckoId
|
|
192
|
+
coingeckId: token.extensions.coingeckoId,
|
|
193
|
+
displayDecimals: 2
|
|
181
194
|
});
|
|
182
195
|
});
|
|
183
196
|
console.log(tokens);
|
|
@@ -553,7 +566,8 @@ var _ZkLend = class _ZkLend extends ILending {
|
|
|
553
566
|
logo: "",
|
|
554
567
|
decimals: pool.token.decimals,
|
|
555
568
|
borrowFactor: Web3Number.fromWei(pool.borrow_factor.value, pool.borrow_factor.decimals),
|
|
556
|
-
collareralFactor
|
|
569
|
+
collareralFactor,
|
|
570
|
+
displayDecimals: 2
|
|
557
571
|
};
|
|
558
572
|
this.tokens.push(token);
|
|
559
573
|
});
|
|
@@ -670,7 +684,7 @@ var PricerFromApi = class extends PricerBase {
|
|
|
670
684
|
try {
|
|
671
685
|
return await this.getPriceFromMyAPI(tokenSymbol);
|
|
672
686
|
} catch (e) {
|
|
673
|
-
logger.warn("getPriceFromMyAPI error", e);
|
|
687
|
+
logger.warn("getPriceFromMyAPI error", JSON.stringify(e.message || e));
|
|
674
688
|
}
|
|
675
689
|
logger.log("getPrice coinbase", tokenSymbol);
|
|
676
690
|
let retry = 0;
|
|
@@ -690,7 +704,7 @@ var PricerFromApi = class extends PricerBase {
|
|
|
690
704
|
timestamp: /* @__PURE__ */ new Date()
|
|
691
705
|
};
|
|
692
706
|
} catch (e) {
|
|
693
|
-
logger.warn("getPrice coinbase error", e
|
|
707
|
+
logger.warn("getPrice coinbase error", JSON.stringify(e.message || e));
|
|
694
708
|
await new Promise((resolve) => setTimeout(resolve, retry * 1e3));
|
|
695
709
|
}
|
|
696
710
|
}
|
|
@@ -704,9 +718,6 @@ var PricerFromApi = class extends PricerBase {
|
|
|
704
718
|
const priceInfo = await priceInfoRes.json();
|
|
705
719
|
const now = /* @__PURE__ */ new Date();
|
|
706
720
|
const priceTime = new Date(priceInfo.timestamp);
|
|
707
|
-
if (now.getTime() - priceTime.getTime() > 9e5) {
|
|
708
|
-
throw new Error("Price is stale");
|
|
709
|
-
}
|
|
710
721
|
const price = Number(priceInfo.price);
|
|
711
722
|
return {
|
|
712
723
|
price,
|
|
@@ -1870,17 +1881,28 @@ function assert(condition, message) {
|
|
|
1870
1881
|
}
|
|
1871
1882
|
|
|
1872
1883
|
// src/modules/avnu.ts
|
|
1873
|
-
var AvnuWrapper = class {
|
|
1874
|
-
async getQuotes(fromToken, toToken, amountWei, taker) {
|
|
1884
|
+
var AvnuWrapper = class _AvnuWrapper {
|
|
1885
|
+
async getQuotes(fromToken, toToken, amountWei, taker, retry = 0) {
|
|
1886
|
+
const MAX_RETRY = 5;
|
|
1887
|
+
logger.verbose(`${_AvnuWrapper.name}: getQuotes => Getting quotes for ${fromToken} -> ${toToken}, amount: ${amountWei}, taker: ${taker}, retry: ${retry}`);
|
|
1875
1888
|
const params = {
|
|
1876
1889
|
sellTokenAddress: fromToken,
|
|
1877
1890
|
buyTokenAddress: toToken,
|
|
1878
1891
|
sellAmount: amountWei,
|
|
1879
|
-
takerAddress: taker
|
|
1892
|
+
takerAddress: taker,
|
|
1893
|
+
// excludeSources: ['Nostra', 'Haiko(Solvers)']
|
|
1894
|
+
excludeSources: ["Haiko(Solvers)"]
|
|
1895
|
+
// to resolve InvalidOraclePrice error
|
|
1880
1896
|
};
|
|
1881
1897
|
assert(fromToken != toToken, "From and to tokens are the same");
|
|
1882
1898
|
const quotes = await fetchQuotes(params);
|
|
1883
|
-
|
|
1899
|
+
if (quotes.length == 0) {
|
|
1900
|
+
if (retry < MAX_RETRY) {
|
|
1901
|
+
await new Promise((res) => setTimeout(res, 3e3));
|
|
1902
|
+
return await this.getQuotes(fromToken, toToken, amountWei, taker, retry + 1);
|
|
1903
|
+
}
|
|
1904
|
+
throw new Error("no quotes found");
|
|
1905
|
+
}
|
|
1884
1906
|
return quotes[0];
|
|
1885
1907
|
}
|
|
1886
1908
|
async getSwapInfo(quote, taker, integratorFeeBps, integratorFeeRecipient, minAmount) {
|
|
@@ -3553,10 +3575,10 @@ var BaseStrategy = class {
|
|
|
3553
3575
|
async getTVL() {
|
|
3554
3576
|
throw new Error("Not implemented");
|
|
3555
3577
|
}
|
|
3556
|
-
depositCall(amountInfo, receiver) {
|
|
3578
|
+
async depositCall(amountInfo, receiver) {
|
|
3557
3579
|
throw new Error("Not implemented");
|
|
3558
3580
|
}
|
|
3559
|
-
withdrawCall(amountInfo, receiver, owner) {
|
|
3581
|
+
async withdrawCall(amountInfo, receiver, owner) {
|
|
3560
3582
|
throw new Error("Not implemented");
|
|
3561
3583
|
}
|
|
3562
3584
|
};
|
|
@@ -3593,7 +3615,7 @@ var VesuRebalance = class _VesuRebalance extends BaseStrategy {
|
|
|
3593
3615
|
* @param receiver - Address that will receive the strategy tokens
|
|
3594
3616
|
* @returns Populated contract call for deposit
|
|
3595
3617
|
*/
|
|
3596
|
-
depositCall(amountInfo, receiver) {
|
|
3618
|
+
async depositCall(amountInfo, receiver) {
|
|
3597
3619
|
assert(amountInfo.tokenInfo.address.eq(this.asset().address), "Deposit token mismatch");
|
|
3598
3620
|
const assetContract = new Contract4(vesu_rebalance_abi_default, this.asset().address.address, this.config.provider);
|
|
3599
3621
|
const call1 = assetContract.populate("approve", [this.address.address, uint2563.bnToUint256(amountInfo.amount.toWei())]);
|
|
@@ -3607,7 +3629,7 @@ var VesuRebalance = class _VesuRebalance extends BaseStrategy {
|
|
|
3607
3629
|
* @param owner - Address that owns the strategy tokens
|
|
3608
3630
|
* @returns Populated contract call for withdrawal
|
|
3609
3631
|
*/
|
|
3610
|
-
withdrawCall(amountInfo, receiver, owner) {
|
|
3632
|
+
async withdrawCall(amountInfo, receiver, owner) {
|
|
3611
3633
|
return [this.contract.populate("withdraw", [uint2563.bnToUint256(amountInfo.amount.toWei()), receiver.address, owner.address])];
|
|
3612
3634
|
}
|
|
3613
3635
|
/**
|
|
@@ -4010,7 +4032,7 @@ var VesuRebalanceStrategies = [{
|
|
|
4010
4032
|
}];
|
|
4011
4033
|
|
|
4012
4034
|
// src/strategies/ekubo-cl-vault.ts
|
|
4013
|
-
import { Contract as
|
|
4035
|
+
import { Contract as Contract6, num as num3, uint256 as uint2564 } from "starknet";
|
|
4014
4036
|
|
|
4015
4037
|
// src/data/cl-vault.abi.json
|
|
4016
4038
|
var cl_vault_abi_default = [
|
|
@@ -8911,6 +8933,58 @@ var erc4626_abi_default = [
|
|
|
8911
8933
|
}
|
|
8912
8934
|
];
|
|
8913
8935
|
|
|
8936
|
+
// src/modules/harvests.ts
|
|
8937
|
+
import { Contract as Contract5 } from "starknet";
|
|
8938
|
+
var Harvests = class _Harvests {
|
|
8939
|
+
constructor(config) {
|
|
8940
|
+
this.config = config;
|
|
8941
|
+
}
|
|
8942
|
+
getHarvests(addr) {
|
|
8943
|
+
throw new Error("Not implemented");
|
|
8944
|
+
}
|
|
8945
|
+
async getUnHarvestedRewards(addr) {
|
|
8946
|
+
const rewards = await this.getHarvests(addr);
|
|
8947
|
+
if (rewards.length == 0) return [];
|
|
8948
|
+
const unClaimed = [];
|
|
8949
|
+
const cls = await this.config.provider.getClassAt(rewards[0].rewardsContract.address);
|
|
8950
|
+
for (let reward of rewards) {
|
|
8951
|
+
const contract = new Contract5(cls.abi, reward.rewardsContract.address, this.config.provider);
|
|
8952
|
+
const isClaimed = await contract.call("is_claimed", [reward.claim.id]);
|
|
8953
|
+
logger.verbose(`${_Harvests.name}: isClaimed: ${isClaimed}`);
|
|
8954
|
+
if (isClaimed)
|
|
8955
|
+
return unClaimed;
|
|
8956
|
+
unClaimed.unshift(reward);
|
|
8957
|
+
}
|
|
8958
|
+
return unClaimed;
|
|
8959
|
+
}
|
|
8960
|
+
};
|
|
8961
|
+
var EkuboHarvests = class extends Harvests {
|
|
8962
|
+
async getHarvests(addr) {
|
|
8963
|
+
const STRK = "0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
|
|
8964
|
+
const EKUBO_API = `https://starknet-mainnet-api.ekubo.org/airdrops/${addr.address}?token=${STRK}`;
|
|
8965
|
+
const resultEkubo = await fetch(EKUBO_API);
|
|
8966
|
+
const items = await resultEkubo.json();
|
|
8967
|
+
const rewards = [];
|
|
8968
|
+
for (let i = 0; i < items.length; ++i) {
|
|
8969
|
+
const info = items[i];
|
|
8970
|
+
assert(info.token == STRK, "expected strk token only");
|
|
8971
|
+
rewards.push({
|
|
8972
|
+
rewardsContract: ContractAddr.from(info.contract_address),
|
|
8973
|
+
token: ContractAddr.from(STRK),
|
|
8974
|
+
startDate: new Date(info.start_date),
|
|
8975
|
+
endDate: new Date(info.end_date),
|
|
8976
|
+
claim: {
|
|
8977
|
+
id: info.claim.id,
|
|
8978
|
+
amount: Web3Number.fromWei(info.claim.amount, 18),
|
|
8979
|
+
claimee: ContractAddr.from(info.claim.claimee)
|
|
8980
|
+
},
|
|
8981
|
+
proof: info.proof
|
|
8982
|
+
});
|
|
8983
|
+
}
|
|
8984
|
+
return rewards.sort((a, b) => b.endDate.getTime() - a.endDate.getTime());
|
|
8985
|
+
}
|
|
8986
|
+
};
|
|
8987
|
+
|
|
8914
8988
|
// src/strategies/ekubo-cl-vault.ts
|
|
8915
8989
|
var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
8916
8990
|
/**
|
|
@@ -8927,19 +9001,74 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
8927
9001
|
assert(metadata.depositTokens.length === 2, "EkuboCL only supports 2 deposit token");
|
|
8928
9002
|
this.metadata = metadata;
|
|
8929
9003
|
this.address = metadata.address;
|
|
8930
|
-
this.contract = new
|
|
8931
|
-
this.lstContract = new
|
|
9004
|
+
this.contract = new Contract6(cl_vault_abi_default, this.address.address, this.config.provider);
|
|
9005
|
+
this.lstContract = new Contract6(erc4626_abi_default, this.metadata.additionalInfo.lstContract.address, this.config.provider);
|
|
8932
9006
|
const EKUBO_POSITION = "0x02e0af29598b407c8716b17f6d2795eca1b471413fa03fb145a5e33722184067";
|
|
8933
|
-
this.ekuboPositionsContract = new
|
|
9007
|
+
this.ekuboPositionsContract = new Contract6(ekubo_positions_abi_default, EKUBO_POSITION, this.config.provider);
|
|
8934
9008
|
const EKUBO_MATH = "0x04a72e9e166f6c0e9d800af4dc40f6b6fb4404b735d3f528d9250808b2481995";
|
|
8935
|
-
this.ekuboMathContract = new
|
|
9009
|
+
this.ekuboMathContract = new Contract6(ekubo_math_abi_default, EKUBO_MATH, this.config.provider);
|
|
8936
9010
|
this.avnu = new AvnuWrapper();
|
|
8937
9011
|
}
|
|
8938
|
-
|
|
8939
|
-
|
|
9012
|
+
async matchInputAmounts(amountInfo) {
|
|
9013
|
+
const bounds = await this.getCurrentBounds();
|
|
9014
|
+
const res = await this._getExpectedAmountsForLiquidity(
|
|
9015
|
+
amountInfo.token0.amount,
|
|
9016
|
+
amountInfo.token1.amount,
|
|
9017
|
+
bounds,
|
|
9018
|
+
false
|
|
9019
|
+
);
|
|
9020
|
+
return {
|
|
9021
|
+
token0: {
|
|
9022
|
+
tokenInfo: amountInfo.token0.tokenInfo,
|
|
9023
|
+
amount: res.amount0
|
|
9024
|
+
},
|
|
9025
|
+
token1: {
|
|
9026
|
+
tokenInfo: amountInfo.token1.tokenInfo,
|
|
9027
|
+
amount: res.amount1
|
|
9028
|
+
}
|
|
9029
|
+
};
|
|
9030
|
+
}
|
|
9031
|
+
/** Returns minimum amounts give given two amounts based on what can be added for liq */
|
|
9032
|
+
async getMinDepositAmounts(amountInfo) {
|
|
9033
|
+
const shares = await this.tokensToShares(amountInfo);
|
|
9034
|
+
const { amount0, amount1 } = await this.contract.call("convert_to_assets", [uint2564.bnToUint256(shares.toWei())]);
|
|
9035
|
+
return {
|
|
9036
|
+
token0: {
|
|
9037
|
+
tokenInfo: amountInfo.token0.tokenInfo,
|
|
9038
|
+
amount: Web3Number.fromWei(amount0.toString(), amountInfo.token0.tokenInfo.decimals)
|
|
9039
|
+
},
|
|
9040
|
+
token1: {
|
|
9041
|
+
tokenInfo: amountInfo.token1.tokenInfo,
|
|
9042
|
+
amount: Web3Number.fromWei(amount1.toString(), amountInfo.token1.tokenInfo.decimals)
|
|
9043
|
+
}
|
|
9044
|
+
};
|
|
8940
9045
|
}
|
|
8941
|
-
|
|
8942
|
-
|
|
9046
|
+
async depositCall(amountInfo, receiver) {
|
|
9047
|
+
const updateAmountInfo = await this.getMinDepositAmounts(amountInfo);
|
|
9048
|
+
const token0Contract = new Contract6(erc4626_abi_default, amountInfo.token0.tokenInfo.address.address, this.config.provider);
|
|
9049
|
+
const token1Contract = new Contract6(erc4626_abi_default, amountInfo.token1.tokenInfo.address.address, this.config.provider);
|
|
9050
|
+
const call1 = token0Contract.populate("approve", [this.address.address, uint2564.bnToUint256(updateAmountInfo.token0.amount.toWei())]);
|
|
9051
|
+
const call2 = token1Contract.populate("approve", [this.address.address, uint2564.bnToUint256(updateAmountInfo.token1.amount.toWei())]);
|
|
9052
|
+
const call3 = this.contract.populate("deposit", [uint2564.bnToUint256(updateAmountInfo.token0.amount.toWei()), uint2564.bnToUint256(updateAmountInfo.token1.amount.toWei()), receiver.address]);
|
|
9053
|
+
const calls = [];
|
|
9054
|
+
if (updateAmountInfo.token0.amount.greaterThan(0)) calls.push(call1);
|
|
9055
|
+
if (updateAmountInfo.token1.amount.greaterThan(0)) calls.push(call2);
|
|
9056
|
+
return [...calls, call3];
|
|
9057
|
+
}
|
|
9058
|
+
async tokensToShares(amountInfo) {
|
|
9059
|
+
const shares = await this.contract.call("convert_to_shares", [
|
|
9060
|
+
uint2564.bnToUint256(amountInfo.token0.amount.toWei()),
|
|
9061
|
+
uint2564.bnToUint256(amountInfo.token1.amount.toWei())
|
|
9062
|
+
]);
|
|
9063
|
+
return Web3Number.fromWei(shares.toString(), 18);
|
|
9064
|
+
}
|
|
9065
|
+
async withdrawCall(amountInfo, receiver, owner) {
|
|
9066
|
+
const shares = await this.tokensToShares(amountInfo);
|
|
9067
|
+
logger.verbose(`${_EkuboCLVault.name}: withdrawCall: shares=${shares.toString()}`);
|
|
9068
|
+
return [this.contract.populate("withdraw", [
|
|
9069
|
+
uint2564.bnToUint256(shares.toWei()),
|
|
9070
|
+
receiver.address
|
|
9071
|
+
])];
|
|
8943
9072
|
}
|
|
8944
9073
|
rebalanceCall(newBounds, swapParams) {
|
|
8945
9074
|
return [this.contract.populate("rebalance", [
|
|
@@ -8958,14 +9087,113 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
8958
9087
|
handleFeesCall() {
|
|
8959
9088
|
return [this.contract.populate("handle_fees", [])];
|
|
8960
9089
|
}
|
|
8961
|
-
|
|
8962
|
-
|
|
9090
|
+
/**
|
|
9091
|
+
* Calculates assets before and now in a given token of TVL per share to observe growth
|
|
9092
|
+
* @returns {Promise<number>} The weighted average APY across all pools
|
|
9093
|
+
*/
|
|
9094
|
+
async netAPY(blockIdentifier = "pending", sinceBlocks = 2e4) {
|
|
9095
|
+
const tvlNow = await this._getTVL(blockIdentifier);
|
|
9096
|
+
const supplyNow = await this.totalSupply(blockIdentifier);
|
|
9097
|
+
const priceNow = await this.getCurrentPrice(blockIdentifier);
|
|
9098
|
+
let blockNow = typeof blockIdentifier == "number" ? blockIdentifier : (await this.config.provider.getBlockLatestAccepted()).block_number;
|
|
9099
|
+
const blockNowTime = typeof blockIdentifier == "number" ? (await this.config.provider.getBlockWithTxs(blockIdentifier)).timestamp : (/* @__PURE__ */ new Date()).getTime() / 1e3;
|
|
9100
|
+
const blockBefore = blockNow - sinceBlocks;
|
|
9101
|
+
const adjustedSupplyNow = supplyNow.minus(await this.getHarvestRewardShares(blockBefore, blockNow));
|
|
9102
|
+
let blockBeforeInfo = await this.config.provider.getBlockWithTxs(blockBefore);
|
|
9103
|
+
const tvlBefore = await this._getTVL(blockBefore);
|
|
9104
|
+
const supplyBefore = await this.totalSupply(blockBefore);
|
|
9105
|
+
const priceBefore = await this.getCurrentPrice(blockBefore);
|
|
9106
|
+
const tvlInToken0Now = tvlNow.amount0.multipliedBy(priceNow.price).plus(tvlNow.amount1);
|
|
9107
|
+
const tvlPerShareNow = tvlInToken0Now.multipliedBy(1e18).dividedBy(adjustedSupplyNow);
|
|
9108
|
+
const tvlInToken0Bf = tvlBefore.amount0.multipliedBy(priceBefore.price).plus(tvlBefore.amount1);
|
|
9109
|
+
const tvlPerShareBf = tvlInToken0Bf.multipliedBy(1e18).dividedBy(supplyBefore);
|
|
9110
|
+
const timeDiffSeconds = blockNowTime - blockBeforeInfo.timestamp;
|
|
9111
|
+
logger.verbose(`tvlInToken0Now: ${tvlInToken0Now.toString()}`);
|
|
9112
|
+
logger.verbose(`tvlInToken0Bf: ${tvlInToken0Bf.toString()}`);
|
|
9113
|
+
logger.verbose(`tvlPerShareNow: ${tvlPerShareNow.toString()}`);
|
|
9114
|
+
logger.verbose(`tvlPerShareBf: ${tvlPerShareBf.toString()}`);
|
|
9115
|
+
logger.verbose(`Price before: ${priceBefore.price.toString()}`);
|
|
9116
|
+
logger.verbose(`Price now: ${priceNow.price.toString()}`);
|
|
9117
|
+
logger.verbose(`Supply before: ${supplyBefore.toString()}`);
|
|
9118
|
+
logger.verbose(`Supply now: ${adjustedSupplyNow.toString()}`);
|
|
9119
|
+
logger.verbose(`Time diff in seconds: ${timeDiffSeconds}`);
|
|
9120
|
+
const apyForGivenBlocks = Number(tvlPerShareNow.minus(tvlPerShareBf).multipliedBy(1e4).dividedBy(tvlPerShareBf)) / 1e4;
|
|
9121
|
+
return apyForGivenBlocks * (365 * 24 * 3600) / timeDiffSeconds;
|
|
8963
9122
|
}
|
|
8964
|
-
async
|
|
8965
|
-
const
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
9123
|
+
async getHarvestRewardShares(fromBlock, toBlock) {
|
|
9124
|
+
const len = Number(await this.contract.call("get_total_rewards"));
|
|
9125
|
+
let shares = Web3Number.fromWei(0, 18);
|
|
9126
|
+
for (let i = len - 1; i > 0; --i) {
|
|
9127
|
+
let record = await this.contract.call("get_rewards_info", [i]);
|
|
9128
|
+
logger.verbose(`${_EkuboCLVault.name}: getHarvestRewardShares: ${i}`);
|
|
9129
|
+
console.log(record);
|
|
9130
|
+
const block = Number(record.block_number);
|
|
9131
|
+
if (block < fromBlock) {
|
|
9132
|
+
return shares;
|
|
9133
|
+
} else if (block > toBlock) {
|
|
9134
|
+
continue;
|
|
9135
|
+
} else {
|
|
9136
|
+
shares = shares.plus(Web3Number.fromWei(record.shares.toString(), 18));
|
|
9137
|
+
}
|
|
9138
|
+
logger.verbose(`${_EkuboCLVault.name}: getHarvestRewardShares: ${i} => ${shares.toWei()}`);
|
|
9139
|
+
}
|
|
9140
|
+
return shares;
|
|
9141
|
+
}
|
|
9142
|
+
async balanceOf(user, blockIdentifier = "pending") {
|
|
9143
|
+
let bal = await this.contract.call("balance_of", [user.address]);
|
|
9144
|
+
return Web3Number.fromWei(bal.toString(), 18);
|
|
9145
|
+
}
|
|
9146
|
+
async getUserTVL(user, blockIdentifier = "pending") {
|
|
9147
|
+
let bal = await this.balanceOf(user, blockIdentifier);
|
|
9148
|
+
const assets = await this.contract.call("convert_to_assets", [uint2564.bnToUint256(bal.toWei())], {
|
|
9149
|
+
blockIdentifier
|
|
9150
|
+
});
|
|
9151
|
+
const poolKey = await this.getPoolKey(blockIdentifier);
|
|
9152
|
+
this.assertValidDepositTokens(poolKey);
|
|
9153
|
+
const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
|
|
9154
|
+
const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
|
|
9155
|
+
const amount0 = Web3Number.fromWei(assets.amount0.toString(), token0Info.decimals);
|
|
9156
|
+
const amount1 = Web3Number.fromWei(assets.amount1.toString(), token1Info.decimals);
|
|
9157
|
+
const P0 = await this.pricer.getPrice(token0Info.symbol);
|
|
9158
|
+
const P1 = await this.pricer.getPrice(token1Info.symbol);
|
|
9159
|
+
const token0Usd = Number(amount0.toFixed(13)) * P0.price;
|
|
9160
|
+
const token1Usd = Number(amount1.toFixed(13)) * P1.price;
|
|
9161
|
+
return {
|
|
9162
|
+
usdValue: token0Usd + token1Usd,
|
|
9163
|
+
token0: {
|
|
9164
|
+
tokenInfo: token0Info,
|
|
9165
|
+
amount: amount0,
|
|
9166
|
+
usdValue: token0Usd
|
|
9167
|
+
},
|
|
9168
|
+
token1: {
|
|
9169
|
+
tokenInfo: token1Info,
|
|
9170
|
+
amount: amount1,
|
|
9171
|
+
usdValue: token1Usd
|
|
9172
|
+
}
|
|
9173
|
+
};
|
|
9174
|
+
}
|
|
9175
|
+
async _getTVL(blockIdentifier = "pending") {
|
|
9176
|
+
const result = await this.contract.call("total_liquidity", [], {
|
|
9177
|
+
blockIdentifier
|
|
9178
|
+
});
|
|
9179
|
+
const bounds = await this.getCurrentBounds(blockIdentifier);
|
|
9180
|
+
const { amount0, amount1 } = await this.getLiquidityToAmounts(Web3Number.fromWei(result.toString(), 18), bounds, blockIdentifier);
|
|
9181
|
+
return { amount0, amount1 };
|
|
9182
|
+
}
|
|
9183
|
+
async totalSupply(blockIdentifier = "pending") {
|
|
9184
|
+
const res = await this.contract.call("total_supply", [], {
|
|
9185
|
+
blockIdentifier
|
|
9186
|
+
});
|
|
9187
|
+
return Web3Number.fromWei(res.toString(), 18);
|
|
9188
|
+
}
|
|
9189
|
+
assertValidDepositTokens(poolKey) {
|
|
9190
|
+
assert(poolKey.token0.eq(this.metadata.depositTokens[0].address), "Expected token0 in depositTokens[0]");
|
|
9191
|
+
assert(poolKey.token1.eq(this.metadata.depositTokens[1].address), "Expected token1 in depositTokens[1]");
|
|
9192
|
+
}
|
|
9193
|
+
async getTVL(blockIdentifier = "pending") {
|
|
9194
|
+
const { amount0, amount1 } = await this._getTVL(blockIdentifier);
|
|
9195
|
+
const poolKey = await this.getPoolKey(blockIdentifier);
|
|
9196
|
+
this.assertValidDepositTokens(poolKey);
|
|
8969
9197
|
const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
|
|
8970
9198
|
const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
|
|
8971
9199
|
const P0 = await this.pricer.getPrice(token0Info.symbol);
|
|
@@ -8973,7 +9201,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
8973
9201
|
const token0Usd = Number(amount0.toFixed(13)) * P0.price;
|
|
8974
9202
|
const token1Usd = Number(amount1.toFixed(13)) * P1.price;
|
|
8975
9203
|
return {
|
|
8976
|
-
|
|
9204
|
+
usdValue: token0Usd + token1Usd,
|
|
8977
9205
|
token0: {
|
|
8978
9206
|
tokenInfo: token0Info,
|
|
8979
9207
|
amount: amount0,
|
|
@@ -9013,7 +9241,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9013
9241
|
const token0Usd = Number(token0Web3.toFixed(13)) * P0.price;
|
|
9014
9242
|
const token1Usd = Number(token1Web3.toFixed(13)) * P1.price;
|
|
9015
9243
|
return {
|
|
9016
|
-
|
|
9244
|
+
usdValue: token0Usd + token1Usd,
|
|
9017
9245
|
token0: {
|
|
9018
9246
|
tokenInfo: token0Info,
|
|
9019
9247
|
amount: token0Web3,
|
|
@@ -9035,11 +9263,11 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9035
9263
|
const truePrice = Number(BigInt(result.toString()) * BigInt(1e9) / BigInt(1e18)) / 1e9;
|
|
9036
9264
|
return truePrice;
|
|
9037
9265
|
}
|
|
9038
|
-
async getCurrentPrice() {
|
|
9039
|
-
const poolKey = await this.getPoolKey();
|
|
9040
|
-
return this._getCurrentPrice(poolKey);
|
|
9266
|
+
async getCurrentPrice(blockIdentifier = "pending") {
|
|
9267
|
+
const poolKey = await this.getPoolKey(blockIdentifier);
|
|
9268
|
+
return this._getCurrentPrice(poolKey, blockIdentifier);
|
|
9041
9269
|
}
|
|
9042
|
-
async _getCurrentPrice(poolKey) {
|
|
9270
|
+
async _getCurrentPrice(poolKey, blockIdentifier = "pending") {
|
|
9043
9271
|
const priceInfo = await this.ekuboPositionsContract.call("get_pool_price", [
|
|
9044
9272
|
{
|
|
9045
9273
|
token0: poolKey.token0.address,
|
|
@@ -9048,35 +9276,44 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9048
9276
|
tick_spacing: poolKey.tick_spacing,
|
|
9049
9277
|
extension: poolKey.extension
|
|
9050
9278
|
}
|
|
9051
|
-
]
|
|
9279
|
+
], {
|
|
9280
|
+
blockIdentifier
|
|
9281
|
+
});
|
|
9052
9282
|
const sqrtRatio = _EkuboCLVault.div2Power128(BigInt(priceInfo.sqrt_ratio.toString()));
|
|
9283
|
+
console.log(`EkuboCLVault: getCurrentPrice: blockIdentifier: ${blockIdentifier}, sqrtRatio: ${sqrtRatio}, ${priceInfo.sqrt_ratio.toString()}`);
|
|
9053
9284
|
const price = sqrtRatio * sqrtRatio;
|
|
9054
9285
|
const tick = _EkuboCLVault.priceToTick(price, true, Number(poolKey.tick_spacing));
|
|
9286
|
+
console.log(`EkuboCLVault: getCurrentPrice: blockIdentifier: ${blockIdentifier}, price: ${price}, tick: ${tick.mag}, ${tick.sign}`);
|
|
9055
9287
|
return {
|
|
9056
9288
|
price,
|
|
9057
|
-
tick: tick.mag * (tick.sign == 0 ? 1 : -1)
|
|
9289
|
+
tick: tick.mag * (tick.sign == 0 ? 1 : -1),
|
|
9290
|
+
sqrtRatio: priceInfo.sqrt_ratio.toString()
|
|
9058
9291
|
};
|
|
9059
9292
|
}
|
|
9060
|
-
async getCurrentBounds() {
|
|
9061
|
-
const result = await this.contract.call("get_position_key", []
|
|
9293
|
+
async getCurrentBounds(blockIdentifier = "pending") {
|
|
9294
|
+
const result = await this.contract.call("get_position_key", [], {
|
|
9295
|
+
blockIdentifier
|
|
9296
|
+
});
|
|
9062
9297
|
return {
|
|
9063
9298
|
lowerTick: _EkuboCLVault.i129ToNumber(result.bounds.lower),
|
|
9064
9299
|
upperTick: _EkuboCLVault.i129ToNumber(result.bounds.upper)
|
|
9065
9300
|
};
|
|
9066
9301
|
}
|
|
9067
|
-
static div2Power128(
|
|
9068
|
-
return Number(BigInt(
|
|
9302
|
+
static div2Power128(num4) {
|
|
9303
|
+
return Number(BigInt(num4.toString()) * 1000000n / BigInt(2 ** 128)) / 1e6;
|
|
9069
9304
|
}
|
|
9070
9305
|
static priceToTick(price, isRoundDown, tickSpacing) {
|
|
9071
9306
|
const value = isRoundDown ? Math.floor(Math.log(price) / Math.log(1.000001)) : Math.ceil(Math.log(price) / Math.log(1.000001));
|
|
9072
9307
|
const tick = Math.floor(value / tickSpacing) * tickSpacing;
|
|
9073
9308
|
return this.tickToi129(tick);
|
|
9074
9309
|
}
|
|
9075
|
-
async getPoolKey() {
|
|
9310
|
+
async getPoolKey(blockIdentifier = "pending") {
|
|
9076
9311
|
if (this.poolKey) {
|
|
9077
9312
|
return this.poolKey;
|
|
9078
9313
|
}
|
|
9079
|
-
const result = await this.contract.call("get_settings", []
|
|
9314
|
+
const result = await this.contract.call("get_settings", [], {
|
|
9315
|
+
blockIdentifier
|
|
9316
|
+
});
|
|
9080
9317
|
const poolKey = {
|
|
9081
9318
|
token0: ContractAddr.from(result.pool_key.token0.toString()),
|
|
9082
9319
|
token1: ContractAddr.from(result.pool_key.token1.toString()),
|
|
@@ -9107,25 +9344,24 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9107
9344
|
* @param amount1: amount of token1
|
|
9108
9345
|
* @returns {amount0, amount1}
|
|
9109
9346
|
*/
|
|
9110
|
-
async _getExpectedAmountsForLiquidity(amount0, amount1, bounds) {
|
|
9347
|
+
async _getExpectedAmountsForLiquidity(amount0, amount1, bounds, justUseInputAmount = true) {
|
|
9111
9348
|
assert(amount0.greaterThan(0) || amount1.greaterThan(0), "Amount is 0");
|
|
9112
|
-
const
|
|
9113
|
-
const sampleLiq = 1e18;
|
|
9349
|
+
const sampleLiq = 1e20;
|
|
9114
9350
|
const { amount0: sampleAmount0, amount1: sampleAmount1 } = await this.getLiquidityToAmounts(Web3Number.fromWei(sampleLiq.toString(), 18), bounds);
|
|
9115
9351
|
logger.verbose(`${_EkuboCLVault.name}: _getExpectedAmountsForLiquidity => sampleAmount0: ${sampleAmount0.toString()}, sampleAmount1: ${sampleAmount1.toString()}`);
|
|
9116
|
-
assert(!sampleAmount0.eq(0)
|
|
9352
|
+
assert(!sampleAmount0.eq(0) || !sampleAmount1.eq(0), "Sample amount is 0");
|
|
9117
9353
|
const price = await (await this.getCurrentPrice()).price;
|
|
9118
9354
|
logger.verbose(`${_EkuboCLVault.name}: _getExpectedAmountsForLiquidity => price: ${price}`);
|
|
9119
9355
|
if (amount1.eq(0) && amount0.greaterThan(0)) {
|
|
9120
9356
|
if (sampleAmount1.eq(0)) {
|
|
9121
9357
|
return {
|
|
9122
9358
|
amount0,
|
|
9123
|
-
amount1: Web3Number.fromWei("0",
|
|
9359
|
+
amount1: Web3Number.fromWei("0", amount1.decimals),
|
|
9124
9360
|
ratio: Infinity
|
|
9125
9361
|
};
|
|
9126
9362
|
} else if (sampleAmount0.eq(0)) {
|
|
9127
9363
|
return {
|
|
9128
|
-
amount0: Web3Number.fromWei("0",
|
|
9364
|
+
amount0: Web3Number.fromWei("0", amount0.decimals),
|
|
9129
9365
|
amount1: amount0.multipliedBy(price),
|
|
9130
9366
|
ratio: 0
|
|
9131
9367
|
};
|
|
@@ -9133,21 +9369,41 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9133
9369
|
} else if (amount0.eq(0) && amount1.greaterThan(0)) {
|
|
9134
9370
|
if (sampleAmount0.eq(0)) {
|
|
9135
9371
|
return {
|
|
9136
|
-
amount0: Web3Number.fromWei("0",
|
|
9372
|
+
amount0: Web3Number.fromWei("0", amount0.decimals),
|
|
9137
9373
|
amount1,
|
|
9138
9374
|
ratio: 0
|
|
9139
9375
|
};
|
|
9140
9376
|
} else if (sampleAmount1.eq(0)) {
|
|
9141
9377
|
return {
|
|
9142
9378
|
amount0: amount1.dividedBy(price),
|
|
9143
|
-
amount1: Web3Number.fromWei("0",
|
|
9379
|
+
amount1: Web3Number.fromWei("0", amount1.decimals),
|
|
9144
9380
|
ratio: Infinity
|
|
9145
9381
|
};
|
|
9146
9382
|
}
|
|
9147
9383
|
}
|
|
9148
|
-
|
|
9384
|
+
assert(sampleAmount0.decimals == sampleAmount1.decimals, "Sample amounts have different decimals");
|
|
9385
|
+
const ratioWeb3Number = sampleAmount0.multipliedBy(1e18).dividedBy(sampleAmount1.toString()).dividedBy(1e18);
|
|
9386
|
+
const ratio = Number(ratioWeb3Number.toFixed(18));
|
|
9149
9387
|
logger.verbose(`${_EkuboCLVault.name}: ${this.metadata.name} => ratio: ${ratio.toString()}`);
|
|
9150
|
-
|
|
9388
|
+
if (justUseInputAmount)
|
|
9389
|
+
return this._solveExpectedAmountsEq(amount0, amount1, ratioWeb3Number, price);
|
|
9390
|
+
if (amount1.eq(0) && amount0.greaterThan(0)) {
|
|
9391
|
+
const _amount1 = amount0.dividedBy(ratioWeb3Number);
|
|
9392
|
+
return {
|
|
9393
|
+
amount0,
|
|
9394
|
+
amount1: _amount1,
|
|
9395
|
+
ratio
|
|
9396
|
+
};
|
|
9397
|
+
} else if (amount0.eq(0) && amount1.greaterThan(0)) {
|
|
9398
|
+
const _amount0 = amount1.multipliedBy(ratio);
|
|
9399
|
+
return {
|
|
9400
|
+
amount0: _amount0,
|
|
9401
|
+
amount1,
|
|
9402
|
+
ratio
|
|
9403
|
+
};
|
|
9404
|
+
} else {
|
|
9405
|
+
throw new Error("Both amounts are non-zero, cannot compute expected amounts");
|
|
9406
|
+
}
|
|
9151
9407
|
}
|
|
9152
9408
|
_solveExpectedAmountsEq(availableAmount0, availableAmount1, ratio, price) {
|
|
9153
9409
|
const y = ratio.multipliedBy(availableAmount1).minus(availableAmount0).dividedBy(ratio.plus(1 / price));
|
|
@@ -9161,10 +9417,11 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9161
9417
|
async getSwapInfoToHandleUnused(considerRebalance = true) {
|
|
9162
9418
|
const poolKey = await this.getPoolKey();
|
|
9163
9419
|
const erc20Mod = new ERC20(this.config);
|
|
9164
|
-
const token0Bal1 = await erc20Mod.balanceOf(poolKey.token0, this.address.address, 18);
|
|
9165
|
-
const token1Bal1 = await erc20Mod.balanceOf(poolKey.token1, this.address.address, 18);
|
|
9166
9420
|
const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
|
|
9167
9421
|
const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
|
|
9422
|
+
const token0Bal1 = await erc20Mod.balanceOf(poolKey.token0, this.address.address, token0Info.decimals);
|
|
9423
|
+
const token1Bal1 = await erc20Mod.balanceOf(poolKey.token1, this.address.address, token1Info.decimals);
|
|
9424
|
+
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => token0Bal1: ${token0Bal1.toString()}, token1Bal1: ${token1Bal1.toString()}`);
|
|
9168
9425
|
const token0Price = await this.pricer.getPrice(token0Info.symbol);
|
|
9169
9426
|
const token1Price = await this.pricer.getPrice(token1Info.symbol);
|
|
9170
9427
|
const token0PriceUsd = token0Price.price * Number(token0Bal1.toFixed(13));
|
|
@@ -9175,6 +9432,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9175
9432
|
let token0Bal = token0Bal1;
|
|
9176
9433
|
let token1Bal = token1Bal1;
|
|
9177
9434
|
if (considerRebalance) {
|
|
9435
|
+
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => considerRebalance: true`);
|
|
9178
9436
|
const tvl = await this.getTVL();
|
|
9179
9437
|
token0Bal = token0Bal.plus(tvl.token0.amount.toString());
|
|
9180
9438
|
token1Bal = token1Bal.plus(tvl.token1.amount.toString());
|
|
@@ -9184,7 +9442,10 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9184
9442
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => token0Bal: ${token0Bal.toString()}, token1Bal: ${token1Bal.toString()}`);
|
|
9185
9443
|
const newBounds = await this.getNewBounds();
|
|
9186
9444
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => newBounds: ${newBounds.lowerTick}, ${newBounds.upperTick}`);
|
|
9187
|
-
|
|
9445
|
+
return await this.getSwapInfoGivenAmounts(poolKey, token0Bal, token1Bal, newBounds);
|
|
9446
|
+
}
|
|
9447
|
+
async getSwapInfoGivenAmounts(poolKey, token0Bal, token1Bal, bounds) {
|
|
9448
|
+
let expectedAmounts = await this._getExpectedAmountsForLiquidity(token0Bal, token1Bal, bounds);
|
|
9188
9449
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => expectedAmounts: ${expectedAmounts.amount0.toString()}, ${expectedAmounts.amount1.toString()}`);
|
|
9189
9450
|
let retry = 0;
|
|
9190
9451
|
const maxRetry = 10;
|
|
@@ -9205,6 +9466,19 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9205
9466
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => tokenToSell: ${tokenToSell.address}, tokenToBuy: ${tokenToBuy.address}, amountToSell: ${amountToSell.toWei()}`);
|
|
9206
9467
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => remainingSellAmount: ${remainingSellAmount.toString()}`);
|
|
9207
9468
|
logger.verbose(`${_EkuboCLVault.name}: getSwapInfoToHandleUnused => expectedRatio: ${expectedRatio}`);
|
|
9469
|
+
if (amountToSell.eq(0)) {
|
|
9470
|
+
return {
|
|
9471
|
+
token_from_address: tokenToSell.address,
|
|
9472
|
+
token_from_amount: uint2564.bnToUint256(0),
|
|
9473
|
+
token_to_address: tokenToSell.address,
|
|
9474
|
+
token_to_amount: uint2564.bnToUint256(0),
|
|
9475
|
+
token_to_min_amount: uint2564.bnToUint256(0),
|
|
9476
|
+
beneficiary: this.address.address,
|
|
9477
|
+
integrator_fee_amount_bps: 0,
|
|
9478
|
+
integrator_fee_recipient: this.address.address,
|
|
9479
|
+
routes: []
|
|
9480
|
+
};
|
|
9481
|
+
}
|
|
9208
9482
|
const quote = await this.avnu.getQuotes(tokenToSell.address, tokenToBuy.address, amountToSell.toWei(), this.address.address);
|
|
9209
9483
|
if (remainingSellAmount.eq(0)) {
|
|
9210
9484
|
const minAmountOut = Web3Number.fromWei(quote.buyAmount.toString(), tokenToBuyInfo.decimals).multipliedBy(0.9999);
|
|
@@ -9227,6 +9501,73 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9227
9501
|
}
|
|
9228
9502
|
throw new Error("Failed to get swap info");
|
|
9229
9503
|
}
|
|
9504
|
+
/**
|
|
9505
|
+
* Attempts to rebalance the vault by iteratively adjusting swap amounts if initial attempt fails.
|
|
9506
|
+
* Uses binary search approach to find optimal swap amount.
|
|
9507
|
+
*
|
|
9508
|
+
* @param newBounds - The new tick bounds to rebalance to
|
|
9509
|
+
* @param swapInfo - Initial swap parameters for rebalancing
|
|
9510
|
+
* @param acc - Account to estimate gas fees with
|
|
9511
|
+
* @param retry - Current retry attempt number (default 0)
|
|
9512
|
+
* @param adjustmentFactor - Percentage to adjust swap amount by (default 1)
|
|
9513
|
+
* @param isToken0Deficit - Whether token0 balance needs increasing (default true)
|
|
9514
|
+
* @returns Array of contract calls needed for rebalancing
|
|
9515
|
+
* @throws Error if max retries reached without successful rebalance
|
|
9516
|
+
*/
|
|
9517
|
+
async rebalanceIter(swapInfo, acc, estimateCall, retry = 0, adjustmentFactor = 1, isToken0Deficit = true) {
|
|
9518
|
+
const MAX_RETRIES = 20;
|
|
9519
|
+
const MIN_ADJUSTMENT = 1e-3;
|
|
9520
|
+
logger.verbose(
|
|
9521
|
+
`Rebalancing ${this.metadata.name}: retry=${retry}, adjustment=${adjustmentFactor}%, token0Deficit=${isToken0Deficit}`
|
|
9522
|
+
);
|
|
9523
|
+
const fromAmount = uint2564.uint256ToBN(swapInfo.token_from_amount);
|
|
9524
|
+
logger.verbose(
|
|
9525
|
+
`Selling ${fromAmount.toString()} of token ${swapInfo.token_from_address}`
|
|
9526
|
+
);
|
|
9527
|
+
try {
|
|
9528
|
+
const calls = await estimateCall(swapInfo);
|
|
9529
|
+
await acc.estimateInvokeFee(calls);
|
|
9530
|
+
return calls;
|
|
9531
|
+
} catch (err) {
|
|
9532
|
+
if (retry >= MAX_RETRIES) {
|
|
9533
|
+
logger.error(`Rebalance failed after ${MAX_RETRIES} retries`);
|
|
9534
|
+
throw err;
|
|
9535
|
+
}
|
|
9536
|
+
if (adjustmentFactor < MIN_ADJUSTMENT) {
|
|
9537
|
+
logger.error("Adjustment factor too small, likely oscillating");
|
|
9538
|
+
throw new Error("Failed to converge on valid swap amount");
|
|
9539
|
+
}
|
|
9540
|
+
logger.error(`Rebalance attempt ${retry + 1} failed, adjusting swap amount...`);
|
|
9541
|
+
const newSwapInfo = { ...swapInfo };
|
|
9542
|
+
const currentAmount = Web3Number.fromWei(fromAmount.toString(), 18);
|
|
9543
|
+
if (err.message.includes("invalid token0 balance") || err.message.includes("invalid token0 amount")) {
|
|
9544
|
+
logger.verbose("Reducing swap amount - excess token0");
|
|
9545
|
+
newSwapInfo.token_from_amount = uint2564.bnToUint256(
|
|
9546
|
+
currentAmount.multipliedBy((100 - adjustmentFactor) / 100).toWei()
|
|
9547
|
+
);
|
|
9548
|
+
adjustmentFactor = isToken0Deficit ? adjustmentFactor * 2 : adjustmentFactor / 2;
|
|
9549
|
+
isToken0Deficit = true;
|
|
9550
|
+
} else if (err.message.includes("invalid token1 balance") || err.message.includes("invalid token1 amount")) {
|
|
9551
|
+
logger.verbose("Increasing swap amount - excess token1");
|
|
9552
|
+
newSwapInfo.token_from_amount = uint2564.bnToUint256(
|
|
9553
|
+
currentAmount.multipliedBy((100 + adjustmentFactor) / 100).toWei()
|
|
9554
|
+
);
|
|
9555
|
+
adjustmentFactor = isToken0Deficit ? adjustmentFactor / 2 : adjustmentFactor * 2;
|
|
9556
|
+
isToken0Deficit = false;
|
|
9557
|
+
} else {
|
|
9558
|
+
logger.error("Unexpected error:", err);
|
|
9559
|
+
}
|
|
9560
|
+
newSwapInfo.token_to_min_amount = uint2564.bnToUint256("0");
|
|
9561
|
+
return this.rebalanceIter(
|
|
9562
|
+
newSwapInfo,
|
|
9563
|
+
acc,
|
|
9564
|
+
estimateCall,
|
|
9565
|
+
retry + 1,
|
|
9566
|
+
adjustmentFactor,
|
|
9567
|
+
isToken0Deficit
|
|
9568
|
+
);
|
|
9569
|
+
}
|
|
9570
|
+
}
|
|
9230
9571
|
static tickToi129(tick) {
|
|
9231
9572
|
if (tick < 0) {
|
|
9232
9573
|
return {
|
|
@@ -9249,21 +9590,23 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9249
9590
|
static tickToPrice(tick) {
|
|
9250
9591
|
return Math.pow(1.000001, Number(tick));
|
|
9251
9592
|
}
|
|
9252
|
-
async getLiquidityToAmounts(liquidity, bounds) {
|
|
9253
|
-
const currentPrice = await this.getCurrentPrice();
|
|
9254
|
-
const lowerPrice =
|
|
9255
|
-
const upperPrice =
|
|
9593
|
+
async getLiquidityToAmounts(liquidity, bounds, blockIdentifier = "pending", _poolKey = null, _currentPrice = null) {
|
|
9594
|
+
const currentPrice = _currentPrice || await this.getCurrentPrice(blockIdentifier);
|
|
9595
|
+
const lowerPrice = _EkuboCLVault.tickToPrice(bounds.lowerTick);
|
|
9596
|
+
const upperPrice = _EkuboCLVault.tickToPrice(bounds.upperTick);
|
|
9256
9597
|
logger.verbose(`${_EkuboCLVault.name}: getLiquidityToAmounts => currentPrice: ${currentPrice.price}, lowerPrice: ${lowerPrice}, upperPrice: ${upperPrice}`);
|
|
9257
9598
|
const result = await this.ekuboMathContract.call("liquidity_delta_to_amount_delta", [
|
|
9258
|
-
uint2564.bnToUint256(
|
|
9599
|
+
uint2564.bnToUint256(currentPrice.sqrtRatio),
|
|
9259
9600
|
{
|
|
9260
9601
|
mag: liquidity.toWei(),
|
|
9261
9602
|
sign: 0
|
|
9262
9603
|
},
|
|
9263
9604
|
uint2564.bnToUint256(_EkuboCLVault.priceToSqrtRatio(lowerPrice).toString()),
|
|
9264
9605
|
uint2564.bnToUint256(_EkuboCLVault.priceToSqrtRatio(upperPrice).toString())
|
|
9265
|
-
]
|
|
9266
|
-
|
|
9606
|
+
], {
|
|
9607
|
+
blockIdentifier
|
|
9608
|
+
});
|
|
9609
|
+
const poolKey = _poolKey || await this.getPoolKey(blockIdentifier);
|
|
9267
9610
|
const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
|
|
9268
9611
|
const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
|
|
9269
9612
|
const amount0 = Web3Number.fromWei(_EkuboCLVault.i129ToNumber(result.amount0).toString(), token0Info.decimals);
|
|
@@ -9273,37 +9616,116 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
|
|
|
9273
9616
|
amount1
|
|
9274
9617
|
};
|
|
9275
9618
|
}
|
|
9619
|
+
async harvest(acc) {
|
|
9620
|
+
const ekuboHarvests = new EkuboHarvests(this.config);
|
|
9621
|
+
const unClaimedRewards = await ekuboHarvests.getUnHarvestedRewards(this.address);
|
|
9622
|
+
const poolKey = await this.getPoolKey();
|
|
9623
|
+
const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
|
|
9624
|
+
const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
|
|
9625
|
+
const bounds = await this.getCurrentBounds();
|
|
9626
|
+
const calls = [];
|
|
9627
|
+
for (let claim of unClaimedRewards) {
|
|
9628
|
+
const fee = claim.claim.amount.multipliedBy(this.metadata.additionalInfo.feeBps).dividedBy(1e4);
|
|
9629
|
+
const postFeeAmount = claim.claim.amount.minus(fee);
|
|
9630
|
+
const isToken1 = claim.token.eq(poolKey.token1);
|
|
9631
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => Processing claim, isToken1: ${isToken1} amount: ${postFeeAmount.toWei()}`);
|
|
9632
|
+
const token0Amt = isToken1 ? new Web3Number(0, token0Info.decimals) : postFeeAmount;
|
|
9633
|
+
const token1Amt = isToken1 ? postFeeAmount : new Web3Number(0, token0Info.decimals);
|
|
9634
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => token0Amt: ${token0Amt.toString()}, token1Amt: ${token1Amt.toString()}`);
|
|
9635
|
+
const swapInfo = await this.getSwapInfoGivenAmounts(poolKey, token0Amt, token1Amt, bounds);
|
|
9636
|
+
swapInfo.token_to_address = token0Info.address.address;
|
|
9637
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => swapInfo: ${JSON.stringify(swapInfo)}`);
|
|
9638
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => claim: ${JSON.stringify(claim)}`);
|
|
9639
|
+
const harvestEstimateCall = async (swapInfo1) => {
|
|
9640
|
+
const swap1Amount = Web3Number.fromWei(uint2564.uint256ToBN(swapInfo1.token_from_amount).toString(), 18);
|
|
9641
|
+
const remainingAmount = postFeeAmount.minus(swap1Amount);
|
|
9642
|
+
const swapInfo2 = { ...swapInfo, token_from_amount: uint2564.bnToUint256(remainingAmount.toWei()) };
|
|
9643
|
+
swapInfo2.token_to_address = token1Info.address.address;
|
|
9644
|
+
const calldata = [
|
|
9645
|
+
claim.rewardsContract.address,
|
|
9646
|
+
{
|
|
9647
|
+
id: claim.claim.id,
|
|
9648
|
+
amount: claim.claim.amount.toWei(),
|
|
9649
|
+
claimee: claim.claim.claimee.address
|
|
9650
|
+
},
|
|
9651
|
+
claim.proof.map((p) => num3.getDecimalString(p)),
|
|
9652
|
+
swapInfo,
|
|
9653
|
+
swapInfo2
|
|
9654
|
+
];
|
|
9655
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => calldata: ${JSON.stringify(calldata)}`);
|
|
9656
|
+
return [this.contract.populate("harvest", calldata)];
|
|
9657
|
+
};
|
|
9658
|
+
const _callsFinal = await this.rebalanceIter(swapInfo, acc, harvestEstimateCall);
|
|
9659
|
+
logger.verbose(`${_EkuboCLVault.name}: harvest => _callsFinal: ${JSON.stringify(_callsFinal)}`);
|
|
9660
|
+
calls.push(..._callsFinal);
|
|
9661
|
+
}
|
|
9662
|
+
return calls;
|
|
9663
|
+
}
|
|
9664
|
+
async getInvestmentFlows() {
|
|
9665
|
+
const netYield = await this.netAPY();
|
|
9666
|
+
const poolKey = await this.getPoolKey();
|
|
9667
|
+
const linkedFlow = {
|
|
9668
|
+
title: this.metadata.name,
|
|
9669
|
+
subItems: [{ key: "Pool", value: `${(_EkuboCLVault.div2Power128(BigInt(poolKey.fee)) * 100).toFixed(2)}%, ${poolKey.tick_spacing} tick spacing` }],
|
|
9670
|
+
linkedFlows: [],
|
|
9671
|
+
style: { backgroundColor: "#35484f" /* Blue */.valueOf() }
|
|
9672
|
+
};
|
|
9673
|
+
const baseFlow = {
|
|
9674
|
+
id: "base",
|
|
9675
|
+
title: "Your Deposit",
|
|
9676
|
+
subItems: [{ key: `Net yield`, value: `${(netYield * 100).toFixed(2)}%` }, { key: `Performance Fee`, value: `${(this.metadata.additionalInfo.feeBps / 100).toFixed(2)}%` }],
|
|
9677
|
+
linkedFlows: [linkedFlow],
|
|
9678
|
+
style: { backgroundColor: "#6e53dc" /* Purple */.valueOf() }
|
|
9679
|
+
};
|
|
9680
|
+
const rebalanceFlow = {
|
|
9681
|
+
id: "rebalance",
|
|
9682
|
+
title: "Automated Rebalance",
|
|
9683
|
+
subItems: [{
|
|
9684
|
+
key: "Range selection",
|
|
9685
|
+
value: `${this.metadata.additionalInfo.newBounds.lower * Number(poolKey.tick_spacing)} to ${this.metadata.additionalInfo.newBounds.upper * Number(poolKey.tick_spacing)} ticks`
|
|
9686
|
+
}],
|
|
9687
|
+
linkedFlows: [linkedFlow],
|
|
9688
|
+
style: { backgroundColor: "purple" /* Green */.valueOf() }
|
|
9689
|
+
};
|
|
9690
|
+
return [baseFlow, rebalanceFlow];
|
|
9691
|
+
}
|
|
9276
9692
|
};
|
|
9277
|
-
var _description2 = "
|
|
9693
|
+
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.";
|
|
9278
9694
|
var _protocol2 = { name: "Ekubo", logo: "https://app.ekubo.org/favicon.ico" };
|
|
9279
9695
|
var _riskFactor2 = [
|
|
9280
9696
|
{ type: "Smart Contract Risk" /* SMART_CONTRACT_RISK */, value: 0.5, weight: 25 },
|
|
9281
9697
|
{ type: "Impermanent Loss Risk" /* IMPERMANENT_LOSS */, value: 1, weight: 75 }
|
|
9282
9698
|
];
|
|
9699
|
+
var AUDIT_URL2 = "https://assets.strkfarm.com/strkfarm/audit_report_vesu_and_ekubo_strats.pdf";
|
|
9283
9700
|
var EkuboCLVaultStrategies = [{
|
|
9284
9701
|
name: "Ekubo xSTRK/STRK",
|
|
9285
|
-
description: _description2,
|
|
9702
|
+
description: _description2.replace("{{POOL_NAME}}", "xSTRK/STRK"),
|
|
9286
9703
|
address: ContractAddr.from("0x01f083b98674bc21effee29ef443a00c7b9a500fd92cf30341a3da12c73f2324"),
|
|
9287
9704
|
type: "Other",
|
|
9288
|
-
|
|
9705
|
+
// must be same order as poolKey token0 and token1
|
|
9706
|
+
depositTokens: [Global.getDefaultTokens().find((t) => t.symbol === "xSTRK"), Global.getDefaultTokens().find((t) => t.symbol === "STRK")],
|
|
9289
9707
|
protocols: [_protocol2],
|
|
9708
|
+
auditUrl: AUDIT_URL2,
|
|
9290
9709
|
maxTVL: Web3Number.fromWei("0", 18),
|
|
9291
9710
|
risk: {
|
|
9292
9711
|
riskFactor: _riskFactor2,
|
|
9293
9712
|
netRisk: _riskFactor2.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / _riskFactor2.reduce((acc, curr) => acc + curr.weight, 0),
|
|
9294
9713
|
notARisks: getNoRiskTags(_riskFactor2)
|
|
9295
9714
|
},
|
|
9715
|
+
apyMethodology: "APY based on 7-day historical performance, including fees and rewards.",
|
|
9296
9716
|
additionalInfo: {
|
|
9297
9717
|
newBounds: {
|
|
9298
9718
|
lower: -1,
|
|
9299
9719
|
upper: 1
|
|
9300
9720
|
},
|
|
9301
|
-
lstContract: ContractAddr.from("0x028d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a")
|
|
9721
|
+
lstContract: ContractAddr.from("0x028d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a"),
|
|
9722
|
+
feeBps: 1e3
|
|
9302
9723
|
}
|
|
9303
9724
|
}];
|
|
9304
9725
|
export {
|
|
9305
9726
|
AutoCompounderSTRK,
|
|
9306
9727
|
AvnuWrapper,
|
|
9728
|
+
BaseStrategy,
|
|
9307
9729
|
ContractAddr,
|
|
9308
9730
|
ERC20,
|
|
9309
9731
|
EkuboCLVault,
|