@strkfarm/sdk 1.1.70 → 2.0.0-dev.1

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.
Files changed (53) hide show
  1. package/dist/cli.js +2 -2
  2. package/dist/cli.mjs +2 -2
  3. package/dist/index.browser.global.js +66861 -59746
  4. package/dist/index.browser.mjs +24970 -18579
  5. package/dist/index.d.ts +1969 -776
  6. package/dist/index.js +25264 -18850
  7. package/dist/index.mjs +25463 -19089
  8. package/package.json +80 -76
  9. package/src/data/extended-deposit.abi.json +3613 -0
  10. package/src/data/universal-vault.abi.json +135 -20
  11. package/src/dataTypes/address.ts +8 -1
  12. package/src/global.ts +240 -193
  13. package/src/interfaces/common.tsx +26 -2
  14. package/src/modules/ExtendedWrapperSDk/index.ts +62 -0
  15. package/src/modules/ExtendedWrapperSDk/types.ts +311 -0
  16. package/src/modules/ExtendedWrapperSDk/wrapper.ts +395 -0
  17. package/src/modules/avnu.ts +17 -4
  18. package/src/modules/ekubo-quoter.ts +98 -10
  19. package/src/modules/erc20.ts +67 -21
  20. package/src/modules/harvests.ts +16 -29
  21. package/src/modules/index.ts +5 -1
  22. package/src/modules/lst-apr.ts +36 -0
  23. package/src/modules/midas.ts +159 -0
  24. package/src/modules/pricer-from-api.ts +2 -2
  25. package/src/modules/pricer-lst.ts +1 -1
  26. package/src/modules/pricer.ts +3 -38
  27. package/src/modules/token-market-data.ts +202 -0
  28. package/src/node/deployer.ts +1 -36
  29. package/src/strategies/autoCompounderStrk.ts +1 -1
  30. package/src/strategies/base-strategy.ts +20 -3
  31. package/src/strategies/ekubo-cl-vault.tsx +123 -306
  32. package/src/strategies/index.ts +4 -1
  33. package/src/strategies/svk-strategy.ts +247 -0
  34. package/src/strategies/universal-adapters/adapter-optimizer.ts +65 -0
  35. package/src/strategies/universal-adapters/adapter-utils.ts +5 -1
  36. package/src/strategies/universal-adapters/avnu-adapter.ts +418 -0
  37. package/src/strategies/universal-adapters/baseAdapter.ts +181 -153
  38. package/src/strategies/universal-adapters/common-adapter.ts +98 -77
  39. package/src/strategies/universal-adapters/extended-adapter.ts +544 -0
  40. package/src/strategies/universal-adapters/index.ts +5 -1
  41. package/src/strategies/universal-adapters/unused-balance-adapter.ts +109 -0
  42. package/src/strategies/universal-adapters/vesu-adapter.ts +220 -218
  43. package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +924 -0
  44. package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +58 -51
  45. package/src/strategies/universal-lst-muliplier-strategy.tsx +707 -774
  46. package/src/strategies/universal-strategy.tsx +1098 -1180
  47. package/src/strategies/vesu-extended-strategy/services/operationService.ts +28 -0
  48. package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +77 -0
  49. package/src/strategies/vesu-extended-strategy/utils/constants.ts +48 -0
  50. package/src/strategies/vesu-extended-strategy/utils/helper.ts +374 -0
  51. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +992 -0
  52. package/src/strategies/vesu-rebalance.tsx +16 -19
  53. package/src/utils/health-factor-math.ts +11 -5
@@ -37,7 +37,7 @@ export class AvnuWrapper {
37
37
  excludeSources = ['Haiko(Solvers)']
38
38
  ): Promise<Quote> {
39
39
  const MAX_RETRY = 5;
40
- // logger.verbose(`${AvnuWrapper.name}: getQuotes => Getting quotes for ${fromToken} -> ${toToken}, amount: ${amountWei}, taker: ${taker}, retry: ${retry}`);
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
- // 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}`);
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
- constructor(private readonly config: IConfig) {}
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 quote = await axios.get(this.ENDPOINT.replace("{{AMOUNT}}", amount.toWei()).replace("{{TOKEN_FROM_ADDRESS}}", _fromToken).replace("{{TOKEN_TO_ADDRESS}}", _toToken));
48
- console.log(`Ekubo quote from ${_fromToken} to ${_toToken} for ${amount.toString()}: ${JSON.stringify(quote.data)}`);
49
- return quote.data as EkuboQuote;
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
- console.error(error.message, 'dassf', error.data);
52
- if (retry < 10) {
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,9 @@ export class EkuboQuoter {
65
152
  */
66
153
  getVesuMultiplyQuote(quote: EkuboQuote, fromTokenInfo: TokenInfo, toTokenInfo: TokenInfo): Swap[] {
67
154
  return quote.splits.map(split => {
68
- const isNegativeAmount = BigInt(split.amount_specified) < 0n;
155
+ const isNegativeAmount = BigInt(split.amount_specified) <= 0n;
69
156
  const token = isNegativeAmount ? toTokenInfo : fromTokenInfo;
157
+ //console.log("token", token, isNegativeAmount);
70
158
  return {
71
159
  route: split.route.map(_route => ({
72
160
  pool_key: {
@@ -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 '@/data/erc20.abi.json';
3
+ import { Contract, uint256 } from "starknet";
4
+ import ERC20Abi from "@/data/erc20.abi.json";
5
5
 
6
6
  export class ERC20 {
7
- readonly config: IConfig;
7
+ readonly config: IConfig;
8
8
 
9
- constructor(config: IConfig) {
10
- this.config = config;
11
- }
9
+ constructor(config: IConfig) {
10
+ this.config = config;
11
+ }
12
12
 
13
- contract(addr: string | ContractAddr) {
14
- const _addr = typeof addr === 'string' ? addr : addr.address;
15
- return new Contract({abi: ERC20Abi, address: _addr, providerOrAccount: this.config.provider});
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
- async balanceOf(token: string | ContractAddr, address: string | ContractAddr, tokenDecimals: number) {
19
- const contract = this.contract(token);
20
- const balance = await contract.call('balanceOf', [address.toString()]);
21
- return Web3Number.fromWei(balance.toString(), tokenDecimals);
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
- async allowance(token: string | ContractAddr, owner: string | ContractAddr, spender: string | ContractAddr, tokenDecimals: number) {
25
- const contract = this.contract(token);
26
- const allowance = await contract.call('allowance', [owner.toString(), spender.toString()]);
27
- return Web3Number.fromWei(allowance.toString(), tokenDecimals);
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
+ }
@@ -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 sortedRewards = rewards.sort((a, b) => b.endDate.getTime() - a.endDate.getTime());
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
- for (const reward of sortedRewards) {
47
- const contract = new Contract({abi: cls.abi, address: reward.rewardsContract.address, providerOrAccount: this.config.provider});
48
- const isClaimed = await contract.call('is_claimed', [reward.claim.id]);
49
- logger.verbose(`${Harvests.name}: isClaimed: ${isClaimed}, claim id: ${reward.claim.id}, address: ${reward.rewardsContract.address}`);
50
- if (isClaimed) {
51
- continue;
52
- }
53
- // rewards contract must have enough balance to claim
54
- const bal = await (new ERC20(this.config)).balanceOf(reward.token, reward.rewardsContract.address, 18);
55
- if (bal.lessThan(reward.claim.amount)) {
56
- logger.verbose(`${Harvests.name}: balance: ${bal.toString()}, amount: ${reward.claim.amount.toString()}`);
57
- continue;
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
- logger.verbose(`${EkuboHarvests.name}: getHarvests => items length: ${items.length}`);
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);
@@ -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';
@@ -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', JSON.stringify(e.message || e));
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 redis: ${tokenSymbol}`);
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://prod-api-quoter.ekubo.org/23448594291968334/{{AMOUNT}}/{{TOKEN_ADDRESS}}/{{UNDERLYING_ADDRESS}}'; // e.g. xSTRK/STRK
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;