@strkfarm/sdk 2.0.0-dev.4 → 2.0.0-dev.40

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 (77) hide show
  1. package/dist/cli.js +190 -36
  2. package/dist/cli.mjs +188 -34
  3. package/dist/index.browser.global.js +116018 -90768
  4. package/dist/index.browser.mjs +12769 -10876
  5. package/dist/index.d.ts +2222 -1947
  6. package/dist/index.js +13171 -11077
  7. package/dist/index.mjs +13076 -11004
  8. package/package.json +3 -3
  9. package/src/data/avnu.abi.json +840 -0
  10. package/src/data/ekubo-price-fethcer.abi.json +265 -0
  11. package/src/data/redeem-request-nft.abi.json +752 -0
  12. package/src/data/universal-vault.abi.json +8 -7
  13. package/src/dataTypes/_bignumber.ts +13 -4
  14. package/src/dataTypes/bignumber.browser.ts +10 -1
  15. package/src/dataTypes/bignumber.node.ts +10 -1
  16. package/src/dataTypes/index.ts +3 -2
  17. package/src/dataTypes/mynumber.ts +141 -0
  18. package/src/global.ts +96 -41
  19. package/src/index.browser.ts +2 -1
  20. package/src/interfaces/common.tsx +212 -5
  21. package/src/modules/apollo-client-config.ts +28 -0
  22. package/src/modules/avnu.ts +21 -12
  23. package/src/modules/ekubo-pricer.ts +79 -0
  24. package/src/modules/ekubo-quoter.ts +48 -30
  25. package/src/modules/erc20.ts +17 -0
  26. package/src/modules/harvests.ts +43 -29
  27. package/src/modules/index.ts +1 -1
  28. package/src/modules/pragma.ts +23 -8
  29. package/src/modules/pricer-from-api.ts +156 -15
  30. package/src/modules/pricer-lst.ts +1 -1
  31. package/src/modules/pricer.ts +40 -4
  32. package/src/modules/pricerBase.ts +2 -1
  33. package/src/node/deployer.ts +36 -1
  34. package/src/node/pricer-redis.ts +2 -1
  35. package/src/strategies/base-strategy.ts +168 -16
  36. package/src/strategies/constants.ts +8 -3
  37. package/src/strategies/ekubo-cl-vault.tsx +1044 -351
  38. package/src/strategies/factory.ts +199 -0
  39. package/src/strategies/index.ts +5 -3
  40. package/src/strategies/registry.ts +262 -0
  41. package/src/strategies/sensei.ts +353 -9
  42. package/src/strategies/svk-strategy.ts +125 -30
  43. package/src/strategies/token-boosted-xstrk-carry-strategy.tsx +1225 -0
  44. package/src/strategies/types.ts +4 -0
  45. package/src/strategies/universal-adapters/adapter-utils.ts +4 -1
  46. package/src/strategies/universal-adapters/avnu-adapter.ts +196 -272
  47. package/src/strategies/universal-adapters/baseAdapter.ts +263 -251
  48. package/src/strategies/universal-adapters/common-adapter.ts +206 -203
  49. package/src/strategies/universal-adapters/index.ts +10 -8
  50. package/src/strategies/universal-adapters/svk-troves-adapter.ts +511 -0
  51. package/src/strategies/universal-adapters/token-transfer-adapter.ts +200 -0
  52. package/src/strategies/universal-adapters/vesu-adapter.ts +120 -82
  53. package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +525 -0
  54. package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +1098 -712
  55. package/src/strategies/universal-adapters/vesu-position-common.ts +258 -0
  56. package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +18 -3
  57. package/src/strategies/universal-lst-muliplier-strategy.tsx +551 -405
  58. package/src/strategies/universal-strategy.tsx +1487 -1173
  59. package/src/strategies/vesu-rebalance.tsx +252 -152
  60. package/src/strategies/yoloVault.ts +1084 -0
  61. package/src/utils/cacheClass.ts +11 -2
  62. package/src/utils/health-factor-math.ts +33 -1
  63. package/src/utils/index.ts +3 -1
  64. package/src/utils/logger.browser.ts +22 -4
  65. package/src/utils/logger.node.ts +259 -24
  66. package/src/utils/starknet-call-parser.ts +1036 -0
  67. package/src/utils/strategy-utils.ts +61 -0
  68. package/src/modules/ExtendedWrapperSDk/index.ts +0 -62
  69. package/src/modules/ExtendedWrapperSDk/types.ts +0 -311
  70. package/src/modules/ExtendedWrapperSDk/wrapper.ts +0 -395
  71. package/src/strategies/universal-adapters/extended-adapter.ts +0 -661
  72. package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
  73. package/src/strategies/vesu-extended-strategy/services/operationService.ts +0 -34
  74. package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +0 -77
  75. package/src/strategies/vesu-extended-strategy/utils/constants.ts +0 -49
  76. package/src/strategies/vesu-extended-strategy/utils/helper.ts +0 -372
  77. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +0 -1140
@@ -55,6 +55,75 @@ export interface IProtocol {
55
55
  logo: string;
56
56
  }
57
57
 
58
+ export interface ICurator {
59
+ name: string;
60
+ logo: string;
61
+ }
62
+
63
+ export enum StrategyTag {
64
+ META_VAULT = "Meta Vaults",
65
+ LEVERED = "Maxx",
66
+ AUTOMATED_LP = "Ekubo",
67
+ BTC = "BTC"
68
+ }
69
+
70
+ export enum VaultType {
71
+ LOOPING = "Looping",
72
+ META_VAULT = "Meta Vault",
73
+ DELTA_NEUTRAL = "Delta Neutral",
74
+ AUTOMATED_LP = "Automated LP",
75
+ TVA = "Troves Value Averaging",
76
+ }
77
+
78
+ // Security metadata enums
79
+ export enum AuditStatus {
80
+ AUDITED = "Audited",
81
+ NOT_AUDITED = "Not Audited",
82
+ }
83
+
84
+ export enum SourceCodeType {
85
+ OPEN_SOURCE = "Open Source",
86
+ CLOSED_SOURCE = "Closed Source",
87
+ }
88
+
89
+ export enum AccessControlType {
90
+ MULTISIG_ACCOUNT = "Multisig Account",
91
+ STANDARD_ACCOUNT = "Standard Account",
92
+ ROLE_BASED_ACCESS = "Role Based Access",
93
+ }
94
+
95
+ export enum InstantWithdrawalVault {
96
+ YES = "Yes",
97
+ NO = "No",
98
+ }
99
+
100
+ // Security metadata interfaces
101
+ export interface SourceCodeInfo {
102
+ type: SourceCodeType;
103
+ contractLink: string;
104
+ }
105
+
106
+ export interface AccessControlInfo {
107
+ type: AccessControlType;
108
+ addresses: ContractAddr[];
109
+ timeLock?: string;
110
+ }
111
+
112
+ export interface SecurityMetadata {
113
+ auditStatus: AuditStatus;
114
+ sourceCode: SourceCodeInfo;
115
+ accessControl: AccessControlInfo;
116
+ }
117
+
118
+ export interface RedemptionInfo {
119
+ instantWithdrawalVault: InstantWithdrawalVault;
120
+ redemptionsInfo: {
121
+ title: string; // e.g. Upto $1M
122
+ description: string; // e.g. "1-2 hours"
123
+ }[],
124
+ alerts: StrategyAlert[];
125
+ }
126
+
58
127
  export enum FlowChartColors {
59
128
  Green = "purple",
60
129
  Blue = "#35484f",
@@ -66,26 +135,85 @@ export interface FAQ {
66
135
  answer: string | React.ReactNode;
67
136
  }
68
137
 
138
+ export enum StrategyLiveStatus {
139
+ ACTIVE = "Active",
140
+ NEW = "New",
141
+ COMING_SOON = "Coming Soon",
142
+ DEPRECATED = "Deprecated", // active but not recommended
143
+ RETIRED = "Retired", // not active anymore
144
+ HOT = "Hot & New 🔥"
145
+ }
146
+
147
+ export interface StrategyAlert {
148
+ type: "warning" | "info";
149
+ text: string | React.ReactNode;
150
+ tab: "all" | "deposit" | "withdraw";
151
+ }
152
+
153
+ export interface StrategySettings {
154
+ maxTVL?: Web3Number;
155
+ liveStatus?: StrategyLiveStatus;
156
+ isPaused?: boolean;
157
+ isInMaintenance?: boolean;
158
+ isAudited: boolean;
159
+ isInstantWithdrawal?: boolean;
160
+ hideHarvestInfo?: boolean;
161
+ is_promoted?: boolean;
162
+ isTransactionHistDisabled?: boolean;
163
+ quoteToken: TokenInfo;
164
+ hideNetEarnings?: boolean;
165
+ showWithdrawalWarningModal?: boolean;
166
+ alerts?: StrategyAlert[];
167
+ tags?: StrategyTag[];
168
+ }
169
+
170
+ export interface StrategyApyHistoryUIConfig {
171
+ // Defaults to true in UI if omitted.
172
+ showApyHistory?: boolean;
173
+ // Optional message shown when APY history is hidden.
174
+ noApyHistoryMessage?: string;
175
+ }
176
+
69
177
  /**
70
178
  * @property risk.riskFactor.factor - The risk factors that are considered for the strategy.
71
179
  * @property risk.riskFactor.factor - The value of the risk factor from 0 to 10, 0 being the lowest and 10 being the highest.
180
+ * @property security - Security-related metadata including audit status, source code information, and access control details.
181
+ * @property redemptionInfo - Redemption information including instant withdrawal availability and expected redemption times.
72
182
  */
73
183
  export interface IStrategyMetadata<T> {
184
+ id: string;
74
185
  name: string;
75
186
  description: string | React.ReactNode;
187
+ /**
188
+ * Optional UI sort priority. Higher shows earlier.
189
+ * Intended for pinning flagship parent vaults (e.g. BTC above STRK).
190
+ */
191
+ priority?: number;
192
+ /**
193
+ * Optional UI config for the variant intro popup (strategy page).
194
+ * Should be identical across strategies that share the same `parentId`.
195
+ */
196
+ variantIntro?: {
197
+ title: string;
198
+ description: string;
199
+ };
76
200
  address: ContractAddr;
77
201
  launchBlock: number;
78
202
  type: "ERC4626" | "ERC721" | "Other";
203
+ vaultType: {
204
+ type: VaultType;
205
+ description: string;
206
+ };
79
207
  depositTokens: TokenInfo[];
80
208
  protocols: IProtocol[];
81
209
  auditUrl?: string;
82
- maxTVL: Web3Number;
83
210
  risk: {
84
211
  riskFactor: RiskFactor[];
85
212
  netRisk: number;
86
213
  notARisks: RiskType[];
87
214
  };
88
215
  apyMethodology?: string;
216
+ realizedApyMethodology?: string;
89
217
  additionalInfo: T;
90
218
  contractDetails: {
91
219
  address: ContractAddr;
@@ -96,8 +224,34 @@ export interface IStrategyMetadata<T> {
96
224
  points?: {multiplier: number, logo: string, toolTip?: string}[];
97
225
  docs?: string;
98
226
  investmentSteps: string[];
99
- curator?: { name: string, logo: string },
100
- isPreview?: boolean
227
+ curator?: ICurator,
228
+ isPreview?: boolean;
229
+ tags?: StrategyTag[];
230
+ security: SecurityMetadata;
231
+ redemptionInfo: RedemptionInfo;
232
+ usualTimeToEarnings: null | string; // e.g. "2 weeks" // some strats grow like step functions
233
+ usualTimeToEarningsDescription: null | string; // e.g. "LSTs price on DEX goes up roughly every 2 weeks"
234
+ discontinuationInfo?: {
235
+ date?: Date;
236
+ reason?: React.ReactNode | string;
237
+ info?: React.ReactNode | string;
238
+ };
239
+ settings?: StrategySettings;
240
+ apyHistoryUIConfig?: StrategyApyHistoryUIConfig;
241
+ // Legacy field for multi-step strategies (deprecated, use investmentFlows instead)
242
+ actions?: Array<{
243
+ name?: string;
244
+ pool?: {
245
+ protocol?: { name: string; logo: string };
246
+ pool?: { name: string; logos?: string[] };
247
+ apr?: number;
248
+ borrow?: { apr?: number };
249
+ };
250
+ amount?: string | number;
251
+ isDeposit?: boolean;
252
+ }>;
253
+ parentId?: string;
254
+ parentName?: string;
101
255
  }
102
256
 
103
257
  export interface IInvestmentFlow {
@@ -124,6 +278,23 @@ export function getMainnetConfig(
124
278
  };
125
279
  }
126
280
 
281
+ export const getStrategyTagDesciption = (tag: StrategyTag): string => {
282
+ switch (tag) {
283
+ case StrategyTag.META_VAULT:
284
+ return "A meta vault is a vault that auto allocates funds to multiple vaults based on optimal yield opportunities";
285
+ case StrategyTag.LEVERED:
286
+ return "Looping vaults on Endur LSTs with leveraged borrowing of STRK or BTC to increase yield (2-4x higher yield than simply staking)";
287
+ case StrategyTag.AUTOMATED_LP:
288
+ return "Automated LP vaults on Ekubo that rebalance position automatically, ensuring you earn fees efficiently";
289
+ case StrategyTag.BTC:
290
+ return "BTC linked vaults";
291
+ }
292
+ }
293
+
294
+ export const getAllStrategyTags = (): StrategyTag[] => {
295
+ return Object.values(StrategyTag);
296
+ }
297
+
127
298
  export const getRiskExplaination = (riskType: RiskType) => {
128
299
  switch (riskType) {
129
300
  case RiskType.MARKET_RISK:
@@ -196,7 +367,7 @@ export function highlightTextWithLinks(
196
367
  {parts.map((part, i) => {
197
368
  const match = highlights.find(m => m.highlight.toLowerCase() === part.toLowerCase());
198
369
  return match ? (
199
- <a key={i} href={match.link} target="_blank" style={{ color: 'var(--chakra-colors-white)', background: 'var(--chakra-colors-highlight)' }}>
370
+ <a key={i} href={match.link} target="_blank" style={{ color: 'white', background: 'rgba(255, 255, 255, 0.04)' }}>
200
371
  {part}
201
372
  </a>
202
373
  ) : (
@@ -215,6 +386,30 @@ export interface VaultPosition {
215
386
  protocol: IProtocol
216
387
  }
217
388
 
389
+ export interface AmountInfo {
390
+ amount: Web3Number;
391
+ usdValue: number;
392
+ tokenInfo: TokenInfo;
393
+ }
394
+
395
+ export interface AmountsInfo {
396
+ usdValue: number;
397
+ amounts: AmountInfo[];
398
+ }
399
+
400
+ /**
401
+ * Strategy capabilities interface
402
+ * Describes what optional methods a strategy instance supports
403
+ */
404
+ export interface StrategyCapabilities {
405
+ hasMatchInputAmounts: boolean;
406
+ hasNetAPY: boolean;
407
+ hasGetInvestmentFlows: boolean;
408
+ hasGetPendingRewards: boolean;
409
+ hasHarvest: boolean;
410
+ hasRebalance: boolean;
411
+ }
412
+
218
413
  const VesuProtocol: IProtocol = {
219
414
  name: "Vesu",
220
415
  logo: "https://static-assets-8zct.onrender.com/integrations/vesu/logo.png"
@@ -249,6 +444,12 @@ const VaultProtocol: IProtocol = {
249
444
  name: "Vault",
250
445
  logo: ""
251
446
  }
447
+
448
+ const TrovesProtocol: IProtocol = {
449
+ name: "Troves",
450
+ logo: "https://app.troves.fi/favicon.ico"
451
+ };
452
+
252
453
  export const Protocols = {
253
454
  NONE: NoneProtocol,
254
455
  VESU: VesuProtocol,
@@ -256,5 +457,11 @@ export const Protocols = {
256
457
  EXTENDED: ExtendedProtocol,
257
458
  EKUBO: EkuboProtocol,
258
459
  AVNU: AvnuProtocol,
259
- VAULT: VaultProtocol
460
+ VAULT: VaultProtocol,
461
+ TROVES: TrovesProtocol
462
+ }
463
+
464
+ export const UnwrapLabsCurator: ICurator = {
465
+ name: "Unwrap Labs",
466
+ logo: "https://assets.troves.fi/integrations/unwraplabs/white.png"
260
467
  }
@@ -0,0 +1,28 @@
1
+ import { ApolloClient, InMemoryCache } from '@apollo/client';
2
+ import { IConfig } from '@/interfaces';
3
+
4
+ /**
5
+ * Creates an Apollo Client instance configured for the appropriate environment
6
+ * @param config - The application config containing network and stage information
7
+ * @returns Configured Apollo Client instance
8
+ */
9
+ export function createApolloClient(config: IConfig) {
10
+ // Determine the URI based on the environment
11
+ const uri = config.stage === 'production'
12
+ ? 'https://api.troves.fi/'
13
+ : 'http://localhost:4000';
14
+
15
+ return new ApolloClient({
16
+ uri,
17
+ cache: new InMemoryCache(),
18
+ });
19
+ }
20
+
21
+ // Default client for backward compatibility
22
+ const apolloClient = new ApolloClient({
23
+ uri: 'https://api.troves.fi/',
24
+ cache: new InMemoryCache(),
25
+ });
26
+
27
+ export default apolloClient;
28
+
@@ -4,7 +4,7 @@ import { Call, Uint256 } from "starknet";
4
4
  import { AvnuOptions, fetchBuildExecuteTransaction, fetchQuotes, Quote } from "@avnu/avnu-sdk";
5
5
  import { assert } from "../utils";
6
6
  import { logger } from "@/utils/logger";
7
- import { ContractAddr } from "@/dataTypes";
7
+ import { ContractAddr, Web3Number } from "@/dataTypes";
8
8
 
9
9
  export interface Route {
10
10
  token_from: string,
@@ -15,12 +15,12 @@ export interface Route {
15
15
  }
16
16
 
17
17
  export interface SwapInfo {
18
- token_from_address: string,
19
- token_from_amount: Uint256,
20
- token_to_address: string,
21
- token_to_amount: Uint256,
22
- token_to_min_amount: Uint256,
23
- beneficiary: string,
18
+ token_from_address: string,
19
+ token_from_amount: Uint256,
20
+ token_to_address: string,
21
+ token_to_amount: Uint256,
22
+ token_to_min_amount: Uint256,
23
+ beneficiary: string,
24
24
  integrator_fee_amount_bps: number,
25
25
  integrator_fee_recipient: string,
26
26
  routes: Route[]
@@ -59,7 +59,7 @@ export class AvnuWrapper {
59
59
  }
60
60
  throw new Error('no quotes found')
61
61
  }
62
-
62
+
63
63
  return filteredQuotes[0];
64
64
  }
65
65
 
@@ -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),
@@ -140,12 +140,21 @@ export class AvnuWrapper {
140
140
  async getSwapCallData(
141
141
  quote: Pick<Quote, 'quoteId' | 'buyTokenAddress' | 'buyAmount' | 'sellTokenAddress' | 'sellAmount'>,
142
142
  taker: string,
143
+ minAmount?: Web3Number,
143
144
  ) {
144
145
  const calldata = await fetchBuildExecuteTransaction(quote.quoteId, taker, undefined, false);
145
146
  const result = calldata.calls.map((call: Call) => {
146
147
  const data = call.calldata as string[];
147
148
  return data.map(x => BigInt(x));
148
149
  });
150
+
151
+ // override with given min amount out
152
+ if (minAmount) {
153
+ logger.verbose(`AvnuWrapper: minAmount passed ${minAmount.toString()}`)
154
+ const u256 = uint256.bnToUint256(minAmount.toWei());
155
+ result[0][6] = BigInt(u256.low);
156
+ result[0][7] = BigInt(u256.high);
157
+ }
149
158
  return result;
150
159
  }
151
- }
160
+ }
@@ -0,0 +1,79 @@
1
+ import { Contract, RpcProvider, BlockIdentifier } from "starknet";
2
+ import EkuboPricerAbi from '@/data/ekubo-price-fethcer.abi.json';
3
+ import { PricerBase } from "./pricerBase";
4
+ import { IConfig, TokenInfo } from "@/interfaces";
5
+ import { PriceInfo } from "./pricer";
6
+
7
+ export class EkuboPricer extends PricerBase {
8
+ EKUBO_PRICE_FETCHER_ADDRESS = '0x04946fb4ad5237d97bbb1256eba2080c4fe1de156da6a7f83e3b4823bb6d7da1';
9
+ readonly contract: Contract;
10
+ private readonly USDC_ADDRESS = '0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8';
11
+ private readonly USDC_DECIMALS = 6;
12
+
13
+ constructor(config: IConfig, tokens: TokenInfo[]) {
14
+ super(config, tokens);
15
+ this.contract = new Contract({
16
+ abi: EkuboPricerAbi,
17
+ address: this.EKUBO_PRICE_FETCHER_ADDRESS,
18
+ providerOrAccount: config.provider as RpcProvider
19
+ });
20
+ }
21
+
22
+ private div2Power128(num: bigint): number {
23
+ return Number((num * BigInt(1e18)) / BigInt(2 ** 128)) / 1e18;
24
+ }
25
+
26
+ async getPrice(tokenAddr: string, blockIdentifier: BlockIdentifier = 'latest'): Promise<PriceInfo> {
27
+ if (!tokenAddr) {
28
+ throw new Error(`EkuboPricer:getPrice - no token`);
29
+ }
30
+
31
+ // get_prices arguments in order:
32
+ // - quote_token: USDC address (quote token for price calculation)
33
+ // - base_tokens: array containing the base token address/addresses
34
+ // - period: time period in seconds for TWAP (3600 = 1 hour)
35
+ // - min_token: minimum token amount threshold (min liquidity) in 6 Decimals = 1000000)
36
+ const result: any = await this.contract.call(
37
+ 'get_prices',
38
+ [this.USDC_ADDRESS, [tokenAddr], 3600, 1000000],
39
+ { blockIdentifier }
40
+ );
41
+
42
+ if (!result || result.length === 0) {
43
+ throw new Error(`EkuboPricer: No price result returned for ${tokenAddr}`);
44
+ }
45
+
46
+ const priceResult = result[0];
47
+
48
+ if (!priceResult?.variant?.Price) {
49
+ const variant = priceResult?.variant ? Object.keys(priceResult.variant)[0] : 'Unknown';
50
+ throw new Error(`EkuboPricer: Price fetch failed with variant: ${variant}`);
51
+ }
52
+
53
+ const rawPrice = typeof priceResult.variant.Price === 'string'
54
+ ? BigInt(priceResult.variant.Price)
55
+ : priceResult.variant.Price;
56
+
57
+ // Get token info to determine decimals from configured tokens
58
+ const tokenInfo = this.tokens.find(t =>
59
+ t.address.address.toLowerCase() === tokenAddr.toLowerCase()
60
+ );
61
+
62
+ if (!tokenInfo) {
63
+ throw new Error(`Token ${tokenAddr} not found in global tokens`);
64
+ }
65
+
66
+ // Convert from x128 format
67
+ const priceAfterX128 = this.div2Power128(rawPrice);
68
+
69
+ // Adjust for token decimals
70
+ const decimalAdjustment = 10 ** (tokenInfo.decimals - this.USDC_DECIMALS);
71
+ const price = priceAfterX128 * decimalAdjustment;
72
+
73
+ return {
74
+ price,
75
+ timestamp: new Date()
76
+ };
77
+ }
78
+ }
79
+
@@ -41,36 +41,57 @@ export class EkuboQuoter {
41
41
  this.tokenMarketData = new TokenMarketData(pricer, config);
42
42
  }
43
43
 
44
- /**
45
- *
46
- * @param fromToken
47
- * @param toToken
48
- * @param amount Can be negative too, which would mean to get exact amount out
49
- * @returns
50
- */
51
- async getQuote(fromToken: string, toToken: string, amount: Web3Number, retry = 0): Promise<EkuboQuote> {
52
- // let _fromToken = amount.gt(0) ? fromToken : toToken;
53
- // let _toToken = amount.gt(0) ? toToken : fromToken;
54
-
44
+ private async _callQuoterApi(fromToken: string, toToken: string, amount: Web3Number, retry = 0): Promise<EkuboQuote> {
55
45
  try {
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);
46
+ const url = this.ENDPOINT.replace("{{AMOUNT}}", amount.toWei()).replace("{{TOKEN_FROM_ADDRESS}}", fromToken).replace("{{TOKEN_TO_ADDRESS}}", toToken);
47
+ logger.verbose(`EkuboQuoter::_callQuoterApi url: ${url}`);
58
48
  const quote = await axios.get(url);
59
- return quote.data as EkuboQuote;
49
+ // console.log('quote', quote.data);
50
+ // console.log('')
51
+ return quote.data as EkuboQuote;
60
52
  } catch (error: any) {
61
- logger.error(`${error.message} dassf ${error.data}`);
53
+ logger.error(`EkuboQuoter::_callQuoterApi error: ${error.message}`);
62
54
  if (retry < 3) {
63
55
  await new Promise((resolve) => setTimeout(resolve, (retry + 1) * 5000));
64
- return await this.getQuote(fromToken, toToken, amount, retry + 1);
56
+ return await this._callQuoterApi(fromToken, toToken, amount, retry + 1);
65
57
  }
66
58
  throw error;
67
59
  }
68
60
  }
69
61
 
62
+ /**
63
+ * Given exactly `inputAmount` of `fromToken`, how much `toToken` do I receive?
64
+ * @param fromToken - address of the token being sold
65
+ * @param toToken - address of the token being bought
66
+ * @param inputAmount - must be positive (the amount of fromToken to sell)
67
+ * @returns EkuboQuote where `total_calculated` is the output amount (positive)
68
+ */
69
+ async getQuoteExactInput(fromToken: string, toToken: string, inputAmount: Web3Number): Promise<EkuboQuote> {
70
+ if (inputAmount.isNegative() || inputAmount.isZero()) {
71
+ throw new Error(`EkuboQuoter::getQuoteExactInput inputAmount must be positive, got ${inputAmount.toFixed()}`);
72
+ }
73
+ return this._callQuoterApi(fromToken, toToken, inputAmount);
74
+ }
75
+
76
+ /**
77
+ * To receive exactly `outputAmount` of `toToken`, how much `fromToken` must I provide?
78
+ * @param fromToken - address of the token being sold
79
+ * @param toToken - address of the token being bought
80
+ * @param outputAmount - must be positive (the desired amount of toToken to receive)
81
+ * @returns EkuboQuote where `total_calculated` is the required input amount (negative per Ekubo convention)
82
+ */
83
+ async getQuoteExactOutput(fromToken: string, toToken: string, outputAmount: Web3Number): Promise<EkuboQuote> {
84
+ if (outputAmount.isNegative() || outputAmount.isZero()) {
85
+ throw new Error(`EkuboQuoter::getQuoteExactOutput outputAmount must be positive, got ${outputAmount.toFixed()}`);
86
+ }
87
+ const negatedAmount = new Web3Number(outputAmount.multipliedBy(-1).toFixed(Math.min(outputAmount.decimals, 15)), outputAmount.decimals);
88
+ return this._callQuoterApi(toToken, fromToken, negatedAmount);
89
+ }
90
+
70
91
  async getDexPrice(baseToken: TokenInfo, quoteToken: TokenInfo, amount: Web3Number) {
71
92
  const lstTokenInfo = baseToken;
72
93
  const lstUnderlyingTokenInfo = quoteToken;
73
- const quote = await this.getQuote(
94
+ const quote = await this.getQuoteExactInput(
74
95
  lstTokenInfo.address.address,
75
96
  lstUnderlyingTokenInfo.address.address,
76
97
  amount
@@ -95,26 +116,23 @@ export class EkuboQuoter {
95
116
  logger.verbose(`${EkuboQuoter.name}:: LST true Exchange Rate: ${exchangeRate}`);
96
117
  return exchangeRate;
97
118
  }
98
- // debt collateral
119
+ // debt collateral
99
120
  async getSwapLimitAmount(fromToken: TokenInfo, toToken: TokenInfo, amount: Web3Number, max_slippage: number = 0.002): Promise<Web3Number> {
100
121
  const isExactAmountIn = amount.greaterThanOrEqualTo(0);
101
122
  logger.verbose(`${EkuboQuoter.name}::getSwapLimitAmount isExactAmountIn: ${isExactAmountIn}, fromToken: ${fromToken.symbol}, toToken: ${toToken.symbol}, amount: ${amount}`);
102
123
  const isYieldToken = this.tokenMarketData.isAPYSupported(toToken);
103
- console.log("isYieldToken", isYieldToken);
104
-
124
+
105
125
  // if LST, get true exchange rate else use dex price
106
126
  // wbtc
107
- const baseToken = isExactAmountIn ? toToken : fromToken; // fromToken -> wbtc,
108
- const quoteToken = isExactAmountIn ? fromToken : toToken; // toToken -> usdc,
127
+ const baseToken = isExactAmountIn ? toToken : fromToken; // fromToken -> wbtc,
128
+ const quoteToken = isExactAmountIn ? fromToken : toToken; // toToken -> usdc,
109
129
  // need dex price of from token in toToken
110
130
  // from baseToken to underlying token
111
131
  // for withdraw, usdc to btc with amount negative
112
132
  const dexPrice = await this.getDexPrice(baseToken, quoteToken, amount);
113
133
  const trueExchangeRate = isYieldToken ? await this.tokenMarketData.getTruePrice(baseToken) : dexPrice;
114
- console.log("trueExchangeRate", trueExchangeRate);
115
134
  if (isExactAmountIn) {
116
135
  let minLSTReceived = amount.dividedBy(dexPrice).multipliedBy(1 - max_slippage); // used for increase
117
- console.log("minLSTReceived", minLSTReceived);
118
136
  const minLSTReceivedAsPerTruePrice = amount.dividedBy(trueExchangeRate); // execution output to be <= True LST price
119
137
  if (minLSTReceived < minLSTReceivedAsPerTruePrice) {
120
138
  minLSTReceived = minLSTReceivedAsPerTruePrice; // the execution shouldn't be bad than True price logi
@@ -122,26 +140,26 @@ export class EkuboQuoter {
122
140
  logger.verbose(`${EkuboQuoter.name}::getModifyLeverCall minLSTReceivedAsPerTruePrice: ${minLSTReceivedAsPerTruePrice}, minLSTReceived: ${minLSTReceived}`);
123
141
  return minLSTReceived;
124
142
  }
125
-
143
+
126
144
  let maxUsedCollateral = amount.abs().dividedBy(dexPrice).multipliedBy(1 + max_slippage); // +ve for exact amount out, used for decrease
127
145
  const maxUsedCollateralInLST = amount.abs().dividedBy(trueExchangeRate).multipliedBy(1.005); // 0.5% slippage, worst case based on true price
128
146
  logger.verbose(`${EkuboQuoter.name}::getModifyLeverCall maxUsedCollateralInLST: ${maxUsedCollateralInLST}, maxUsedCollateral: ${maxUsedCollateral}`);
129
147
  if (maxUsedCollateralInLST > maxUsedCollateral) {
130
148
  maxUsedCollateral = maxUsedCollateralInLST;
131
149
  }
132
-
150
+
133
151
  return maxUsedCollateral;
134
152
  }
135
153
 
136
154
  /**
137
155
  * Formats Ekubo response for Vesu multiple use
138
- * @param quote
139
- * @param fromTokenInfo
140
- * @returns
156
+ * @param quote
157
+ * @param fromTokenInfo
158
+ * @returns
141
159
  */
142
160
  getVesuMultiplyQuote(quote: EkuboQuote, fromTokenInfo: TokenInfo, toTokenInfo: TokenInfo): Swap[] {
143
161
  return quote.splits.map(split => {
144
-
162
+
145
163
  const isNegativeAmount = BigInt(split.amount_specified) <= 0n;
146
164
  const token = isNegativeAmount ? toTokenInfo : fromTokenInfo;
147
165
  return {
@@ -58,6 +58,23 @@ export class ERC20 {
58
58
  return transferCall;
59
59
  }
60
60
 
61
+ transferFrom(
62
+ token: string | ContractAddr,
63
+ from: string | ContractAddr,
64
+ to: string | ContractAddr,
65
+ amount: Web3Number
66
+ ) {
67
+ const contract = this.contract(token);
68
+ const amountUint256 = uint256.bnToUint256(amount.toWei());
69
+ const transferFromCall = contract.populate("transferFrom", [
70
+ from.toString(),
71
+ to.toString(),
72
+ amountUint256.low.toString(),
73
+ amountUint256.high.toString(),
74
+ ]);
75
+ return transferFromCall;
76
+ }
77
+
61
78
  approve(
62
79
  token: string | ContractAddr,
63
80
  spender: string | ContractAddr,