@strkfarm/sdk 1.1.70 → 2.0.0-dev.2
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 +2 -2
- package/dist/cli.mjs +2 -2
- package/dist/index.browser.global.js +67016 -59681
- package/dist/index.browser.mjs +29832 -23221
- package/dist/index.d.ts +2006 -787
- package/dist/index.js +25403 -18769
- package/dist/index.mjs +25333 -18739
- package/package.json +80 -76
- package/src/data/extended-deposit.abi.json +3613 -0
- package/src/data/universal-vault.abi.json +135 -20
- package/src/dataTypes/address.ts +7 -0
- package/src/global.ts +240 -193
- package/src/interfaces/common.tsx +26 -2
- package/src/modules/ExtendedWrapperSDk/index.ts +62 -0
- package/src/modules/ExtendedWrapperSDk/types.ts +311 -0
- package/src/modules/ExtendedWrapperSDk/wrapper.ts +395 -0
- package/src/modules/avnu.ts +17 -4
- package/src/modules/ekubo-quoter.ts +99 -10
- package/src/modules/erc20.ts +67 -21
- package/src/modules/harvests.ts +16 -29
- package/src/modules/index.ts +5 -1
- package/src/modules/lst-apr.ts +36 -0
- package/src/modules/midas.ts +159 -0
- package/src/modules/pricer-from-api.ts +2 -2
- package/src/modules/pricer-lst.ts +1 -1
- package/src/modules/pricer.ts +3 -38
- package/src/modules/token-market-data.ts +202 -0
- package/src/node/deployer.ts +1 -36
- package/src/strategies/autoCompounderStrk.ts +1 -1
- package/src/strategies/base-strategy.ts +20 -3
- package/src/strategies/ekubo-cl-vault.tsx +123 -306
- package/src/strategies/index.ts +4 -1
- package/src/strategies/svk-strategy.ts +247 -0
- package/src/strategies/universal-adapters/adapter-optimizer.ts +65 -0
- package/src/strategies/universal-adapters/adapter-utils.ts +5 -1
- package/src/strategies/universal-adapters/avnu-adapter.ts +411 -0
- package/src/strategies/universal-adapters/baseAdapter.ts +181 -153
- package/src/strategies/universal-adapters/common-adapter.ts +98 -77
- package/src/strategies/universal-adapters/extended-adapter.ts +661 -0
- package/src/strategies/universal-adapters/index.ts +5 -1
- package/src/strategies/universal-adapters/unused-balance-adapter.ts +109 -0
- package/src/strategies/universal-adapters/vesu-adapter.ts +220 -218
- package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +924 -0
- package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +58 -51
- package/src/strategies/universal-lst-muliplier-strategy.tsx +707 -774
- package/src/strategies/universal-strategy.tsx +1098 -1180
- package/src/strategies/vesu-extended-strategy/services/operationService.ts +34 -0
- package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +77 -0
- package/src/strategies/vesu-extended-strategy/utils/constants.ts +49 -0
- package/src/strategies/vesu-extended-strategy/utils/helper.ts +376 -0
- package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +1134 -0
- package/src/strategies/vesu-rebalance.tsx +16 -19
- package/src/utils/health-factor-math.ts +11 -5
package/src/modules/avnu.ts
CHANGED
|
@@ -37,7 +37,7 @@ export class AvnuWrapper {
|
|
|
37
37
|
excludeSources = ['Haiko(Solvers)']
|
|
38
38
|
): Promise<Quote> {
|
|
39
39
|
const MAX_RETRY = 5;
|
|
40
|
-
|
|
40
|
+
logger.verbose(`${AvnuWrapper.name}: getQuotes => Getting quotes for ${fromToken} -> ${toToken}, amount: ${amountWei}, taker: ${taker}, retry: ${retry}`);
|
|
41
41
|
const params: any = {
|
|
42
42
|
sellTokenAddress: fromToken,
|
|
43
43
|
buyTokenAddress: toToken,
|
|
@@ -100,9 +100,9 @@ export class AvnuWrapper {
|
|
|
100
100
|
// swapInfo as expected by the strategy
|
|
101
101
|
// fallback, max 1% slippage
|
|
102
102
|
const _minAmount = minAmount || (quote.buyAmount * 95n / 100n).toString();
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
logger.verbose(`${AvnuWrapper.name}: getSwapInfo => sellToken: ${quote.sellTokenAddress}, sellAmount: ${quote.sellAmount}`);
|
|
104
|
+
logger.verbose(`${AvnuWrapper.name}: getSwapInfo => buyToken: ${quote.buyTokenAddress}`);
|
|
105
|
+
logger.verbose(`${AvnuWrapper.name}: getSwapInfo => buyAmount: ${quote.buyAmount}, minAmount: ${_minAmount}`);
|
|
106
106
|
const swapInfo: SwapInfo = {
|
|
107
107
|
token_from_address: quote.sellTokenAddress,
|
|
108
108
|
token_from_amount: uint256.bnToUint256(quote.sellAmount),
|
|
@@ -135,4 +135,17 @@ export class AvnuWrapper {
|
|
|
135
135
|
routes: [],
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
async getSwapCallData(
|
|
141
|
+
quote: Pick<Quote, 'quoteId' | 'buyTokenAddress' | 'buyAmount' | 'sellTokenAddress' | 'sellAmount'>,
|
|
142
|
+
taker: string,
|
|
143
|
+
) {
|
|
144
|
+
const calldata = await fetchBuildExecuteTransaction(quote.quoteId, taker, undefined, false);
|
|
145
|
+
const result = calldata.calls.map((call: Call) => {
|
|
146
|
+
const data = call.calldata as string[];
|
|
147
|
+
return data.map(x => BigInt(x));
|
|
148
|
+
});
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
138
151
|
}
|
|
@@ -2,6 +2,12 @@ import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
|
2
2
|
import { IConfig, TokenInfo } from "@/interfaces/common";
|
|
3
3
|
import { Swap } from "@/strategies";
|
|
4
4
|
import axios from "axios";
|
|
5
|
+
import { logger } from "@/utils";
|
|
6
|
+
import { uint256 } from "starknet";
|
|
7
|
+
import { Contract } from "starknet";
|
|
8
|
+
import ERC4626Abi from "@/data/erc4626.abi.json";
|
|
9
|
+
import { TokenMarketData } from "./token-market-data";
|
|
10
|
+
import { PricerBase } from "./pricerBase";
|
|
5
11
|
|
|
6
12
|
export interface EkuboRouteNode {
|
|
7
13
|
pool_key: {
|
|
@@ -29,8 +35,11 @@ export interface EkuboQuote {
|
|
|
29
35
|
|
|
30
36
|
export class EkuboQuoter {
|
|
31
37
|
ENDPOINT = 'https://prod-api-quoter.ekubo.org/23448594291968334/{{AMOUNT}}/{{TOKEN_FROM_ADDRESS}}/{{TOKEN_TO_ADDRESS}}'; // e.g. ETH/USDC'
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
tokenMarketData: TokenMarketData;
|
|
39
|
+
|
|
40
|
+
constructor(private readonly config: IConfig, pricer: PricerBase) {
|
|
41
|
+
this.tokenMarketData = new TokenMarketData(pricer, config);
|
|
42
|
+
}
|
|
34
43
|
|
|
35
44
|
/**
|
|
36
45
|
*
|
|
@@ -40,16 +49,17 @@ export class EkuboQuoter {
|
|
|
40
49
|
* @returns
|
|
41
50
|
*/
|
|
42
51
|
async getQuote(fromToken: string, toToken: string, amount: Web3Number, retry = 0): Promise<EkuboQuote> {
|
|
43
|
-
let _fromToken = amount.gt(0) ? fromToken : toToken;
|
|
44
|
-
let _toToken = amount.gt(0) ? toToken : fromToken;
|
|
52
|
+
// let _fromToken = amount.gt(0) ? fromToken : toToken;
|
|
53
|
+
// let _toToken = amount.gt(0) ? toToken : fromToken;
|
|
45
54
|
|
|
46
55
|
try {
|
|
47
|
-
const
|
|
48
|
-
console.log(
|
|
49
|
-
|
|
56
|
+
const url = this.ENDPOINT.replace("{{AMOUNT}}", amount.toFixed(0)).replace("{{TOKEN_FROM_ADDRESS}}", fromToken).replace("{{TOKEN_TO_ADDRESS}}", toToken);
|
|
57
|
+
console.log("url", url);
|
|
58
|
+
const quote = await axios.get(url);
|
|
59
|
+
return quote.data as EkuboQuote;
|
|
50
60
|
} catch (error: any) {
|
|
51
|
-
|
|
52
|
-
if (retry <
|
|
61
|
+
logger.error(`${error.message} dassf ${error.data}`);
|
|
62
|
+
if (retry < 3) {
|
|
53
63
|
await new Promise((resolve) => setTimeout(resolve, (retry + 1) * 5000));
|
|
54
64
|
return await this.getQuote(fromToken, toToken, amount, retry + 1);
|
|
55
65
|
}
|
|
@@ -57,6 +67,83 @@ export class EkuboQuoter {
|
|
|
57
67
|
}
|
|
58
68
|
}
|
|
59
69
|
|
|
70
|
+
async getDexPrice(baseToken: TokenInfo, quoteToken: TokenInfo, amount: Web3Number) {
|
|
71
|
+
const lstTokenInfo = baseToken;
|
|
72
|
+
const lstUnderlyingTokenInfo = quoteToken;
|
|
73
|
+
console.log("lstTokenInfo", lstTokenInfo);
|
|
74
|
+
console.log("lstUnderlyingTokenInfo", lstUnderlyingTokenInfo);
|
|
75
|
+
console.log("amount", amount);
|
|
76
|
+
const quote = await this.getQuote(
|
|
77
|
+
lstTokenInfo.address.address,
|
|
78
|
+
lstUnderlyingTokenInfo.address.address,
|
|
79
|
+
amount
|
|
80
|
+
);
|
|
81
|
+
console.log("quote", quote);
|
|
82
|
+
// in Underlying
|
|
83
|
+
const outputAmount = Web3Number.fromWei(quote.total_calculated, lstUnderlyingTokenInfo.decimals);
|
|
84
|
+
console.log("outputAmount", outputAmount);
|
|
85
|
+
console.log("amount", amount);
|
|
86
|
+
const price = outputAmount.toNumber() / amount.toNumber();
|
|
87
|
+
console.log("price", price);
|
|
88
|
+
logger.verbose(`${EkuboQuoter.name}:: LST Dex Price: ${price}`);
|
|
89
|
+
return price;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async getLSTTrueExchangeRate(baseToken: TokenInfo, quoteToken: TokenInfo, amount: Web3Number) {
|
|
93
|
+
const lstTokenInfo = baseToken;
|
|
94
|
+
const lstABI = new Contract({
|
|
95
|
+
abi: ERC4626Abi,
|
|
96
|
+
address: lstTokenInfo.address.address,
|
|
97
|
+
providerOrAccount: this.config.provider
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const price: any = await lstABI.call('convert_to_assets', [uint256.bnToUint256((new Web3Number(1, lstTokenInfo.decimals)).toWei())]);
|
|
101
|
+
const exchangeRate = Number(uint256.uint256ToBN(price).toString()) / Math.pow(10, lstTokenInfo.decimals);
|
|
102
|
+
logger.verbose(`${EkuboQuoter.name}:: LST true Exchange Rate: ${exchangeRate}`);
|
|
103
|
+
return exchangeRate;
|
|
104
|
+
}
|
|
105
|
+
// debt collateral
|
|
106
|
+
async getSwapLimitAmount(fromToken: TokenInfo, toToken: TokenInfo, amount: Web3Number, max_slippage: number = 0.002): Promise<Web3Number> {
|
|
107
|
+
const isExactAmountIn = amount.greaterThanOrEqualTo(0);
|
|
108
|
+
console.log("isExactAmountIn", isExactAmountIn);
|
|
109
|
+
logger.verbose(`${EkuboQuoter.name}::getSwapLimitAmount isExactAmountIn: ${isExactAmountIn}, fromToken: ${fromToken.symbol}, toToken: ${toToken.symbol}, amount: ${amount}`);
|
|
110
|
+
const isYieldToken = this.tokenMarketData.isAPYSupported(toToken);
|
|
111
|
+
console.log("isYieldToken", isYieldToken);
|
|
112
|
+
|
|
113
|
+
// if LST, get true exchange rate else use dex price
|
|
114
|
+
// wbtc
|
|
115
|
+
const baseToken = isExactAmountIn ? toToken : fromToken; // fromToken -> wbtc,
|
|
116
|
+
const quoteToken = isExactAmountIn ? fromToken : toToken; // toToken -> usdc,
|
|
117
|
+
console.log("baseToken", baseToken);
|
|
118
|
+
console.log("quoteToken", quoteToken);
|
|
119
|
+
// need dex price of from token in toToken
|
|
120
|
+
// from baseToken to underlying token
|
|
121
|
+
// for withdraw, usdc to btc with amount negative
|
|
122
|
+
const dexPrice = await this.getDexPrice(baseToken, quoteToken, amount);
|
|
123
|
+
console.log("dexPrice", dexPrice);
|
|
124
|
+
const trueExchangeRate = isYieldToken ? await this.tokenMarketData.getTruePrice(baseToken) : dexPrice;
|
|
125
|
+
console.log("trueExchangeRate", trueExchangeRate);
|
|
126
|
+
if (isExactAmountIn) {
|
|
127
|
+
let minLSTReceived = amount.dividedBy(dexPrice).multipliedBy(1 - max_slippage); // used for increase
|
|
128
|
+
console.log("minLSTReceived", minLSTReceived);
|
|
129
|
+
const minLSTReceivedAsPerTruePrice = amount.dividedBy(trueExchangeRate); // execution output to be <= True LST price
|
|
130
|
+
if (minLSTReceived < minLSTReceivedAsPerTruePrice) {
|
|
131
|
+
minLSTReceived = minLSTReceivedAsPerTruePrice; // the execution shouldn't be bad than True price logi
|
|
132
|
+
}
|
|
133
|
+
logger.verbose(`${EkuboQuoter.name}::getModifyLeverCall minLSTReceivedAsPerTruePrice: ${minLSTReceivedAsPerTruePrice}, minLSTReceived: ${minLSTReceived}`);
|
|
134
|
+
return minLSTReceived;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let maxUsedCollateral = amount.abs().dividedBy(dexPrice).multipliedBy(1 + max_slippage); // +ve for exact amount out, used for decrease
|
|
138
|
+
const maxUsedCollateralInLST = amount.abs().dividedBy(trueExchangeRate).multipliedBy(1.005); // 0.5% slippage, worst case based on true price
|
|
139
|
+
logger.verbose(`${EkuboQuoter.name}::getModifyLeverCall maxUsedCollateralInLST: ${maxUsedCollateralInLST}, maxUsedCollateral: ${maxUsedCollateral}`);
|
|
140
|
+
if (maxUsedCollateralInLST > maxUsedCollateral) {
|
|
141
|
+
maxUsedCollateral = maxUsedCollateralInLST;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return maxUsedCollateral;
|
|
145
|
+
}
|
|
146
|
+
|
|
60
147
|
/**
|
|
61
148
|
* Formats Ekubo response for Vesu multiple use
|
|
62
149
|
* @param quote
|
|
@@ -65,8 +152,10 @@ export class EkuboQuoter {
|
|
|
65
152
|
*/
|
|
66
153
|
getVesuMultiplyQuote(quote: EkuboQuote, fromTokenInfo: TokenInfo, toTokenInfo: TokenInfo): Swap[] {
|
|
67
154
|
return quote.splits.map(split => {
|
|
68
|
-
|
|
155
|
+
|
|
156
|
+
const isNegativeAmount = BigInt(split.amount_specified) <= 0n;
|
|
69
157
|
const token = isNegativeAmount ? toTokenInfo : fromTokenInfo;
|
|
158
|
+
console.log("token for withdrawal", token, isNegativeAmount);
|
|
70
159
|
return {
|
|
71
160
|
route: split.route.map(_route => ({
|
|
72
161
|
pool_key: {
|
package/src/modules/erc20.ts
CHANGED
|
@@ -1,29 +1,75 @@
|
|
|
1
1
|
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
2
2
|
import { IConfig } from "@/interfaces";
|
|
3
|
-
import { Contract } from "starknet";
|
|
4
|
-
import ERC20Abi from
|
|
3
|
+
import { Contract, uint256 } from "starknet";
|
|
4
|
+
import ERC20Abi from "@/data/erc20.abi.json";
|
|
5
5
|
|
|
6
6
|
export class ERC20 {
|
|
7
|
-
|
|
7
|
+
readonly config: IConfig;
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
constructor(config: IConfig) {
|
|
10
|
+
this.config = config;
|
|
11
|
+
}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
contract(addr: string | ContractAddr) {
|
|
14
|
+
const _addr = typeof addr === "string" ? addr : addr.address;
|
|
15
|
+
return new Contract({
|
|
16
|
+
abi: ERC20Abi,
|
|
17
|
+
address: _addr,
|
|
18
|
+
providerOrAccount: this.config.provider,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
async balanceOf(
|
|
23
|
+
token: string | ContractAddr,
|
|
24
|
+
address: string | ContractAddr,
|
|
25
|
+
tokenDecimals: number
|
|
26
|
+
) {
|
|
27
|
+
const contract = this.contract(token);
|
|
28
|
+
const balance = await contract.call("balance_of", [address.toString()]);
|
|
29
|
+
return Web3Number.fromWei(balance.toString(), tokenDecimals);
|
|
30
|
+
}
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
async allowance(
|
|
33
|
+
token: string | ContractAddr,
|
|
34
|
+
owner: string | ContractAddr,
|
|
35
|
+
spender: string | ContractAddr,
|
|
36
|
+
tokenDecimals: number
|
|
37
|
+
) {
|
|
38
|
+
const contract = this.contract(token);
|
|
39
|
+
const allowance = await contract.call("allowance", [
|
|
40
|
+
owner.toString(),
|
|
41
|
+
spender.toString(),
|
|
42
|
+
]);
|
|
43
|
+
return Web3Number.fromWei(allowance.toString(), tokenDecimals);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
transfer(
|
|
47
|
+
token: string | ContractAddr,
|
|
48
|
+
to: string | ContractAddr,
|
|
49
|
+
amount: Web3Number
|
|
50
|
+
) {
|
|
51
|
+
const contract = this.contract(token);
|
|
52
|
+
const amountUint256 = uint256.bnToUint256(amount.toWei());
|
|
53
|
+
const transferCall = contract.populate("transfer", [
|
|
54
|
+
to.toString(),
|
|
55
|
+
amountUint256.low.toString(),
|
|
56
|
+
amountUint256.high.toString(),
|
|
57
|
+
]);
|
|
58
|
+
return transferCall;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
approve(
|
|
62
|
+
token: string | ContractAddr,
|
|
63
|
+
spender: string | ContractAddr,
|
|
64
|
+
amount: Web3Number
|
|
65
|
+
) {
|
|
66
|
+
const contract = this.contract(token);
|
|
67
|
+
const amountUint256 = uint256.bnToUint256(amount.toWei());
|
|
68
|
+
const approveCall = contract.populate("approve", [
|
|
69
|
+
spender.toString(),
|
|
70
|
+
amountUint256.low.toString(),
|
|
71
|
+
amountUint256.high.toString(),
|
|
72
|
+
]);
|
|
73
|
+
return approveCall;
|
|
74
|
+
}
|
|
75
|
+
}
|
package/src/modules/harvests.ts
CHANGED
|
@@ -29,36 +29,28 @@ export class Harvests {
|
|
|
29
29
|
|
|
30
30
|
async getUnHarvestedRewards(addr: ContractAddr) {
|
|
31
31
|
const rewards = await this.getHarvests(addr);
|
|
32
|
-
logger.verbose(`${Harvests.name}: getUnHarvestedRewards => rewards length: ${rewards.length}`);
|
|
33
32
|
if (rewards.length == 0) return [];
|
|
34
33
|
|
|
35
34
|
const unClaimed: HarvestInfo[] = [];
|
|
36
35
|
|
|
37
36
|
// use the latest one
|
|
38
|
-
const
|
|
39
|
-
if (sortedRewards.length == 0) {
|
|
40
|
-
logger.verbose(`${Harvests.name}: no rewards found`);
|
|
41
|
-
return [];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const cls = await this.config.provider.getClassAt(sortedRewards[0].rewardsContract.address);
|
|
37
|
+
const reward = rewards.sort((a, b) => b.endDate.getTime() - a.endDate.getTime())[0];
|
|
45
38
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
unClaimed.push(reward); // to ensure older harvest is first
|
|
39
|
+
const cls = await this.config.provider.getClassAt(reward.rewardsContract.address);
|
|
40
|
+
const contract = new Contract({abi: cls.abi, address: reward.rewardsContract.address, providerOrAccount: this.config.provider});
|
|
41
|
+
const isClaimed = await contract.call('is_claimed', [reward.claim.id]);
|
|
42
|
+
logger.verbose(`${Harvests.name}: isClaimed: ${isClaimed}`);
|
|
43
|
+
if (isClaimed) {
|
|
44
|
+
return unClaimed;
|
|
45
|
+
}
|
|
46
|
+
// rewards contract must have enough balance to claim
|
|
47
|
+
const bal = await (new ERC20(this.config)).balanceOf(reward.token, reward.rewardsContract.address, 18);
|
|
48
|
+
if (bal.lessThan(reward.claim.amount)) {
|
|
49
|
+
logger.verbose(`${Harvests.name}: balance: ${bal.toString()}, amount: ${reward.claim.amount.toString()}`);
|
|
50
|
+
return unClaimed;
|
|
61
51
|
}
|
|
52
|
+
|
|
53
|
+
unClaimed.unshift(reward); // to ensure older harvest is first
|
|
62
54
|
return unClaimed;
|
|
63
55
|
}
|
|
64
56
|
}
|
|
@@ -67,11 +59,10 @@ const STRK = '0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d'
|
|
|
67
59
|
|
|
68
60
|
export class EkuboHarvests extends Harvests {
|
|
69
61
|
async getHarvests(addr: ContractAddr) {
|
|
70
|
-
logger.verbose(`${EkuboHarvests.name}: getHarvests => addr: ${addr.address}`);
|
|
71
62
|
const EKUBO_API = `https://starknet-mainnet-api.ekubo.org/airdrops/${addr.address}?token=${STRK}`
|
|
72
63
|
const resultEkubo = await fetch(EKUBO_API);
|
|
73
64
|
const items = (await resultEkubo.json());
|
|
74
|
-
|
|
65
|
+
|
|
75
66
|
const rewards: HarvestInfo[] = [];
|
|
76
67
|
for (let i=0; i<items.length; ++i) {
|
|
77
68
|
const info = items[i];
|
|
@@ -110,10 +101,6 @@ export class VesuHarvests extends Harvests {
|
|
|
110
101
|
logger.verbose(`${VesuHarvests.name}: claimed_amount: ${claimed_amount.toString()}`);
|
|
111
102
|
|
|
112
103
|
const data = _data.data['defiSpring'];
|
|
113
|
-
if (!data) {
|
|
114
|
-
logger.verbose(`${VesuHarvests.name}: no defiSpring data found`);
|
|
115
|
-
return [];
|
|
116
|
-
}
|
|
117
104
|
|
|
118
105
|
// get the actual reward
|
|
119
106
|
const actualReward = Web3Number.fromWei(data.amount, 18).minus(claimed_amount);
|
package/src/modules/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from './token-market-data';
|
|
1
2
|
export * from './pricer';
|
|
2
3
|
export * from './pragma';
|
|
3
4
|
export * from './zkLend';
|
|
@@ -6,4 +7,7 @@ export * from './erc20';
|
|
|
6
7
|
export * from './avnu';
|
|
7
8
|
export * from './ekubo-quoter';
|
|
8
9
|
export * from './pricer-lst';
|
|
9
|
-
export * from './lst-apr';
|
|
10
|
+
export * from './lst-apr';
|
|
11
|
+
export * from './pricerBase';
|
|
12
|
+
export * from './midas';
|
|
13
|
+
export * from './ExtendedWrapperSDk';
|
package/src/modules/lst-apr.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ContractAddr } from "@/dataTypes";
|
|
2
|
+
import { Global } from "@/global";
|
|
3
|
+
import { TokenInfo } from "@/interfaces";
|
|
2
4
|
import { logger } from "@/utils";
|
|
3
5
|
|
|
4
6
|
export interface LSTStats {
|
|
@@ -19,6 +21,19 @@ export class LSTAPRService {
|
|
|
19
21
|
private static cacheTimestamp: number = 0;
|
|
20
22
|
private static readonly CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
|
|
21
23
|
|
|
24
|
+
static lstMapping: Record<string, TokenInfo> = {};
|
|
25
|
+
|
|
26
|
+
static isLST(address: ContractAddr): boolean {
|
|
27
|
+
return this.lstMapping[address.address] !== undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static getUnderlyingFromLST(lstAddress: ContractAddr): TokenInfo {
|
|
31
|
+
const underlying = this.lstMapping[lstAddress.address];
|
|
32
|
+
if (!underlying) {
|
|
33
|
+
throw new Error(`Underlying token not found for ${lstAddress.address}`);
|
|
34
|
+
}
|
|
35
|
+
return underlying;
|
|
36
|
+
}
|
|
22
37
|
/**
|
|
23
38
|
* Fetches LST stats from Endur API with caching
|
|
24
39
|
* @returns Promise<LSTStats[]> Array of LST statistics
|
|
@@ -138,3 +153,24 @@ export class LSTAPRService {
|
|
|
138
153
|
logger.verbose(`LSTAPRService: Cache cleared`);
|
|
139
154
|
}
|
|
140
155
|
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
// populate lstMapping
|
|
159
|
+
const lstSymbolMapping: any = {
|
|
160
|
+
xSTRK: "STRK",
|
|
161
|
+
xWBTC: "WBTC",
|
|
162
|
+
xtBTC: "tBTC",
|
|
163
|
+
xsBTC: "solvBTC",
|
|
164
|
+
xLBTC: "LBTC",
|
|
165
|
+
}
|
|
166
|
+
Object.keys(lstSymbolMapping).forEach(key => {
|
|
167
|
+
const lst = Global.getDefaultTokens().find(t => t.symbol === key);
|
|
168
|
+
if (!lst) {
|
|
169
|
+
throw new Error(`LST token not found for ${key}`);
|
|
170
|
+
}
|
|
171
|
+
const underlying = Global.getDefaultTokens().find(t => t.symbol === lstSymbolMapping[key]);
|
|
172
|
+
if (!underlying) {
|
|
173
|
+
throw new Error(`Underlying token not found for ${key}`);
|
|
174
|
+
}
|
|
175
|
+
LSTAPRService.lstMapping[lst.address.address] = underlying;
|
|
176
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { ContractAddr } from '../dataTypes';
|
|
3
|
+
import { logger } from '../utils/logger';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Midas module for interacting with Midas API
|
|
7
|
+
* Provides functions to get APY, price, and TVL data for Midas tokens
|
|
8
|
+
*/
|
|
9
|
+
export class Midas {
|
|
10
|
+
// Static mapping of contract addresses to Midas API symbols
|
|
11
|
+
private static readonly CONTRACT_TO_SYMBOL: Record<string, string> = {
|
|
12
|
+
'0x4e4fb1a9ca7e84bae609b9dc0078ad7719e49187ae7e425bb47d131710eddac': 'mre7btc', // mRe7BTC
|
|
13
|
+
'0x4be8945e61dc3e19ebadd1579a6bd53b262f51ba89e6f8b0c4bc9a7e3c633fc': 'mre7', // mRe7YIELD
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
private static readonly BASE_URL = 'https://api-prod.midas.app/api/data';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Check if a contract address is supported by Midas
|
|
20
|
+
* @param contractAddr The contract address to check
|
|
21
|
+
* @returns True if the contract address is supported
|
|
22
|
+
*/
|
|
23
|
+
static isSupported(contractAddr: ContractAddr): boolean {
|
|
24
|
+
return contractAddr.address in Midas.CONTRACT_TO_SYMBOL;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the Midas symbol for a given contract address
|
|
29
|
+
* @param contractAddr The contract address to look up
|
|
30
|
+
* @returns The Midas symbol for the contract
|
|
31
|
+
* @throws Error if contract address is not found
|
|
32
|
+
*/
|
|
33
|
+
static getSymbolFromContract(contractAddr: ContractAddr): string {
|
|
34
|
+
const symbol = Midas.CONTRACT_TO_SYMBOL[contractAddr.address];
|
|
35
|
+
if (!symbol) {
|
|
36
|
+
throw new Error(`Contract address ${contractAddr.address} not found in Midas mapping`);
|
|
37
|
+
}
|
|
38
|
+
return symbol;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get APY data for all Midas tokens
|
|
43
|
+
* @returns Object with token symbols as keys and APY values as numbers
|
|
44
|
+
* @throws Error if API request fails
|
|
45
|
+
*/
|
|
46
|
+
static async getAPYs(): Promise<Record<string, number>> {
|
|
47
|
+
try {
|
|
48
|
+
const response = await axios.get(`${Midas.BASE_URL}/apys`);
|
|
49
|
+
return response.data;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
logger.error('Failed to fetch APYs from Midas API:', error);
|
|
52
|
+
throw new Error(`Failed to fetch APYs: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get APY for a specific token by contract address
|
|
58
|
+
* @param contractAddr The contract address of the token
|
|
59
|
+
* @returns The APY value for the token
|
|
60
|
+
* @throws Error if contract address not found or API request fails
|
|
61
|
+
*/
|
|
62
|
+
static async getAPY(contractAddr: ContractAddr): Promise<number> {
|
|
63
|
+
const symbol = Midas.getSymbolFromContract(contractAddr);
|
|
64
|
+
const apys = await Midas.getAPYs();
|
|
65
|
+
|
|
66
|
+
if (!(symbol in apys)) {
|
|
67
|
+
throw new Error(`Symbol ${symbol} not found in APY data`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return apys[symbol];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get price data for a specific token
|
|
75
|
+
* @param contractAddr The contract address of the token
|
|
76
|
+
* @param timestampFrom Optional start timestamp (defaults to 30 days ago)
|
|
77
|
+
* @param timestampTo Optional end timestamp (defaults to now)
|
|
78
|
+
* @param environment Environment (defaults to 'mainnet')
|
|
79
|
+
* @returns The latest price for the token
|
|
80
|
+
* @throws Error if contract address not found or API request fails
|
|
81
|
+
*/
|
|
82
|
+
static async getPrice(
|
|
83
|
+
contractAddr: ContractAddr,
|
|
84
|
+
timestampFrom?: number,
|
|
85
|
+
timestampTo?: number,
|
|
86
|
+
environment: string = 'mainnet'
|
|
87
|
+
): Promise<number> {
|
|
88
|
+
const symbol = Midas.getSymbolFromContract(contractAddr);
|
|
89
|
+
|
|
90
|
+
// Default to 30 days ago if not provided
|
|
91
|
+
const from = timestampFrom ?? Math.floor(Date.now() / 1000) - (30 * 24 * 60 * 60);
|
|
92
|
+
// Default to now if not provided
|
|
93
|
+
const to = timestampTo ?? Math.floor(Date.now() / 1000);
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const response = await axios.get(`${Midas.BASE_URL}/${symbol}/price`, {
|
|
97
|
+
params: {
|
|
98
|
+
timestampFrom: from,
|
|
99
|
+
timestampTo: to,
|
|
100
|
+
environment
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const priceData = response.data;
|
|
105
|
+
if (!Array.isArray(priceData) || priceData.length === 0) {
|
|
106
|
+
throw new Error(`No price data found for ${symbol}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Return the latest price (first item in the array)
|
|
110
|
+
return priceData[0].price;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
logger.error(`Failed to fetch price for ${symbol}:`, error);
|
|
113
|
+
throw new Error(`Failed to fetch price for ${symbol}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get TVL data for all tokens
|
|
119
|
+
* @param environment Environment (defaults to 'mainnet')
|
|
120
|
+
* @returns Object with TVL data including totalTvl and tokenTvl
|
|
121
|
+
* @throws Error if API request fails
|
|
122
|
+
*/
|
|
123
|
+
static async getTVLData(environment: string = 'mainnet'): Promise<{
|
|
124
|
+
lastUpdatedAt: string;
|
|
125
|
+
totalTvl: number;
|
|
126
|
+
tokenTvl: Record<string, any>;
|
|
127
|
+
}> {
|
|
128
|
+
try {
|
|
129
|
+
const response = await axios.get(`${Midas.BASE_URL}/tvl`, {
|
|
130
|
+
params: { environment }
|
|
131
|
+
});
|
|
132
|
+
return response.data;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
logger.error('Failed to fetch TVL data from Midas API:', error);
|
|
135
|
+
throw new Error(`Failed to fetch TVL data: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get TVL for a specific token by contract address
|
|
141
|
+
* @param contractAddr The contract address of the token
|
|
142
|
+
* @param environment Environment (defaults to 'mainnet')
|
|
143
|
+
* @returns The TVL data for the token (USD and native amounts)
|
|
144
|
+
* @throws Error if contract address not found or API request fails
|
|
145
|
+
*/
|
|
146
|
+
static async getTVL(
|
|
147
|
+
contractAddr: ContractAddr,
|
|
148
|
+
environment: string = 'mainnet'
|
|
149
|
+
): Promise<{ usd: number; native: number } | number> {
|
|
150
|
+
const symbol = Midas.getSymbolFromContract(contractAddr);
|
|
151
|
+
const tvlData = await Midas.getTVLData(environment);
|
|
152
|
+
|
|
153
|
+
if (!(symbol in tvlData.tokenTvl)) {
|
|
154
|
+
throw new Error(`Symbol ${symbol} not found in TVL data`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return tvlData.tokenTvl[symbol];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -13,7 +13,7 @@ export class PricerFromApi extends PricerBase {
|
|
|
13
13
|
try {
|
|
14
14
|
return await this.getPriceFromMyAPI(tokenSymbol);
|
|
15
15
|
} catch (e: any) {
|
|
16
|
-
logger.warn('getPriceFromMyAPI error',
|
|
16
|
+
logger.warn('getPriceFromMyAPI error', e);
|
|
17
17
|
}
|
|
18
18
|
logger.info('getPrice coinbase', tokenSymbol);
|
|
19
19
|
let retry = 0;
|
|
@@ -41,7 +41,7 @@ export class PricerFromApi extends PricerBase {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
async getPriceFromMyAPI(tokenSymbol: string) {
|
|
44
|
-
logger.verbose(`getPrice from
|
|
44
|
+
logger.verbose(`getPrice from api: ${tokenSymbol}`);
|
|
45
45
|
const endpoint = 'https://proxy.api.troves.fi'
|
|
46
46
|
const url = `${endpoint}/api/price/${tokenSymbol}`;
|
|
47
47
|
const priceInfoRes = await fetch(url);
|
|
@@ -6,7 +6,7 @@ import axios from "axios";
|
|
|
6
6
|
|
|
7
7
|
export class PricerLST extends Pricer {
|
|
8
8
|
private tokenMaps: {lst: TokenInfo, underlying: TokenInfo}[];
|
|
9
|
-
protected EKUBO_API = 'https://
|
|
9
|
+
protected EKUBO_API = 'https://quoter-mainnet-api.ekubo.org/{{AMOUNT}}/{{TOKEN_ADDRESS}}/{{UNDERLYING_ADDRESS}}'; // e.g. xSTRK/STRK
|
|
10
10
|
|
|
11
11
|
constructor(config: IConfig, tokenMaps: {lst: TokenInfo, underlying: TokenInfo}[]) {
|
|
12
12
|
const refreshInterval = 5000;
|