@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.
- package/dist/cli.js +190 -36
- package/dist/cli.mjs +188 -34
- package/dist/index.browser.global.js +116018 -90768
- package/dist/index.browser.mjs +12769 -10876
- package/dist/index.d.ts +2222 -1947
- package/dist/index.js +13171 -11077
- package/dist/index.mjs +13076 -11004
- package/package.json +3 -3
- package/src/data/avnu.abi.json +840 -0
- package/src/data/ekubo-price-fethcer.abi.json +265 -0
- package/src/data/redeem-request-nft.abi.json +752 -0
- package/src/data/universal-vault.abi.json +8 -7
- package/src/dataTypes/_bignumber.ts +13 -4
- package/src/dataTypes/bignumber.browser.ts +10 -1
- package/src/dataTypes/bignumber.node.ts +10 -1
- package/src/dataTypes/index.ts +3 -2
- package/src/dataTypes/mynumber.ts +141 -0
- package/src/global.ts +96 -41
- package/src/index.browser.ts +2 -1
- package/src/interfaces/common.tsx +212 -5
- package/src/modules/apollo-client-config.ts +28 -0
- package/src/modules/avnu.ts +21 -12
- package/src/modules/ekubo-pricer.ts +79 -0
- package/src/modules/ekubo-quoter.ts +48 -30
- package/src/modules/erc20.ts +17 -0
- package/src/modules/harvests.ts +43 -29
- package/src/modules/index.ts +1 -1
- package/src/modules/pragma.ts +23 -8
- package/src/modules/pricer-from-api.ts +156 -15
- package/src/modules/pricer-lst.ts +1 -1
- package/src/modules/pricer.ts +40 -4
- package/src/modules/pricerBase.ts +2 -1
- package/src/node/deployer.ts +36 -1
- package/src/node/pricer-redis.ts +2 -1
- package/src/strategies/base-strategy.ts +168 -16
- package/src/strategies/constants.ts +8 -3
- package/src/strategies/ekubo-cl-vault.tsx +1044 -351
- package/src/strategies/factory.ts +199 -0
- package/src/strategies/index.ts +5 -3
- package/src/strategies/registry.ts +262 -0
- package/src/strategies/sensei.ts +353 -9
- package/src/strategies/svk-strategy.ts +125 -30
- package/src/strategies/token-boosted-xstrk-carry-strategy.tsx +1225 -0
- package/src/strategies/types.ts +4 -0
- package/src/strategies/universal-adapters/adapter-utils.ts +4 -1
- package/src/strategies/universal-adapters/avnu-adapter.ts +196 -272
- package/src/strategies/universal-adapters/baseAdapter.ts +263 -251
- package/src/strategies/universal-adapters/common-adapter.ts +206 -203
- package/src/strategies/universal-adapters/index.ts +10 -8
- package/src/strategies/universal-adapters/svk-troves-adapter.ts +511 -0
- package/src/strategies/universal-adapters/token-transfer-adapter.ts +200 -0
- package/src/strategies/universal-adapters/vesu-adapter.ts +120 -82
- package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +525 -0
- package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +1098 -712
- package/src/strategies/universal-adapters/vesu-position-common.ts +258 -0
- package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +18 -3
- package/src/strategies/universal-lst-muliplier-strategy.tsx +551 -405
- package/src/strategies/universal-strategy.tsx +1487 -1173
- package/src/strategies/vesu-rebalance.tsx +252 -152
- package/src/strategies/yoloVault.ts +1084 -0
- package/src/utils/cacheClass.ts +11 -2
- package/src/utils/health-factor-math.ts +33 -1
- package/src/utils/index.ts +3 -1
- package/src/utils/logger.browser.ts +22 -4
- package/src/utils/logger.node.ts +259 -24
- package/src/utils/starknet-call-parser.ts +1036 -0
- package/src/utils/strategy-utils.ts +61 -0
- package/src/modules/ExtendedWrapperSDk/index.ts +0 -62
- package/src/modules/ExtendedWrapperSDk/types.ts +0 -311
- package/src/modules/ExtendedWrapperSDk/wrapper.ts +0 -395
- package/src/strategies/universal-adapters/extended-adapter.ts +0 -661
- package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
- package/src/strategies/vesu-extended-strategy/services/operationService.ts +0 -34
- package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +0 -77
- package/src/strategies/vesu-extended-strategy/utils/constants.ts +0 -49
- package/src/strategies/vesu-extended-strategy/utils/helper.ts +0 -372
- 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?:
|
|
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: '
|
|
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
|
+
|
package/src/modules/avnu.ts
CHANGED
|
@@ -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.
|
|
57
|
-
|
|
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
|
-
|
|
49
|
+
// console.log('quote', quote.data);
|
|
50
|
+
// console.log('')
|
|
51
|
+
return quote.data as EkuboQuote;
|
|
60
52
|
} catch (error: any) {
|
|
61
|
-
logger.error(
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 {
|
package/src/modules/erc20.ts
CHANGED
|
@@ -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,
|