@strkfarm/sdk 2.0.0-staging.7 → 2.0.0-staging.71
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/index.browser.global.js +3150 -1041
- package/dist/index.browser.mjs +2839 -722
- package/dist/index.d.ts +351 -50
- package/dist/index.js +2934 -805
- package/dist/index.mjs +2842 -722
- package/package.json +4 -4
- package/src/data/universal-vault.abi.json +143 -27
- package/src/dataTypes/_bignumber.ts +5 -0
- package/src/dataTypes/bignumber.browser.ts +5 -0
- package/src/dataTypes/bignumber.node.ts +5 -0
- package/src/global.ts +61 -8
- package/src/interfaces/common.tsx +85 -27
- package/src/modules/avnu.ts +1 -1
- package/src/modules/erc20.ts +18 -2
- package/src/modules/index.ts +3 -1
- package/src/modules/pricer-avnu-api.ts +114 -0
- package/src/modules/pricer.ts +76 -46
- package/src/node/pricer-redis.ts +1 -0
- package/src/strategies/base-strategy.ts +153 -8
- package/src/strategies/constants.ts +2 -2
- package/src/strategies/ekubo-cl-vault.tsx +257 -91
- package/src/strategies/factory.ts +21 -1
- package/src/strategies/index.ts +2 -0
- package/src/strategies/registry.ts +15 -30
- package/src/strategies/sensei.ts +52 -13
- package/src/strategies/types.ts +4 -0
- package/src/strategies/universal-adapters/vesu-adapter.ts +46 -25
- package/src/strategies/universal-lst-muliplier-strategy.tsx +1464 -584
- package/src/strategies/universal-strategy.tsx +160 -81
- package/src/strategies/vesu-rebalance.tsx +22 -12
- package/src/strategies/yoloVault.ts +1084 -0
- package/src/utils/strategy-utils.ts +6 -2
|
@@ -25,6 +25,8 @@ export interface RiskFactor {
|
|
|
25
25
|
reason?: string; // optional reason for the risk factor
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export type PriceMethod = 'AvnuApi' | 'Coinbase' | 'Coinmarketcap' | 'Ekubo' | 'Avnu';
|
|
29
|
+
|
|
28
30
|
export interface TokenInfo {
|
|
29
31
|
name: string;
|
|
30
32
|
symbol: string;
|
|
@@ -35,6 +37,8 @@ export interface TokenInfo {
|
|
|
35
37
|
displayDecimals: number;
|
|
36
38
|
priceProxySymbol?: string; // for tokens like illiquid tokens, we use a proxy symbol to get the price
|
|
37
39
|
priceCheckAmount?: number; // for tokens like BTC, doing 1BTC price check may not be ideal, esp on illiquid netwrks like sn
|
|
40
|
+
priceMethod?: PriceMethod; // preferred price source; tried first, then falls back to other methods
|
|
41
|
+
dontPrice?: boolean; // a flag that skips pricer to check these tokens.
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
export enum Network {
|
|
@@ -55,25 +59,24 @@ export interface IProtocol {
|
|
|
55
59
|
logo: string;
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
BTC = "btc",
|
|
62
|
-
META_VAULTS = "meta-vaults",
|
|
62
|
+
export interface ICurator {
|
|
63
|
+
name: string;
|
|
64
|
+
logo: string;
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
export enum StrategyTag {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
SENSEI = "Sensei",
|
|
71
|
-
ENDUR = "Endur",
|
|
72
|
-
BTC = "BTC"
|
|
68
|
+
META_VAULT = "Meta Vaults",
|
|
69
|
+
LEVERED = "Maxx",
|
|
70
|
+
AUTOMATED_LP = "Ekubo",
|
|
71
|
+
BTC = "BTC"
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
export enum VaultType {
|
|
76
|
-
|
|
75
|
+
LOOPING = "Looping",
|
|
76
|
+
META_VAULT = "Meta Vault",
|
|
77
|
+
DELTA_NEUTRAL = "Delta Neutral",
|
|
78
|
+
AUTOMATED_LP = "Automated LP",
|
|
79
|
+
TVA = "Troves Value Averaging",
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
// Security metadata enums
|
|
@@ -90,6 +93,7 @@ export enum SourceCodeType {
|
|
|
90
93
|
export enum AccessControlType {
|
|
91
94
|
MULTISIG_ACCOUNT = "Multisig Account",
|
|
92
95
|
STANDARD_ACCOUNT = "Standard Account",
|
|
96
|
+
ROLE_BASED_ACCESS = "Role Based Access",
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
export enum InstantWithdrawalVault {
|
|
@@ -106,7 +110,7 @@ export interface SourceCodeInfo {
|
|
|
106
110
|
export interface AccessControlInfo {
|
|
107
111
|
type: AccessControlType;
|
|
108
112
|
addresses: ContractAddr[];
|
|
109
|
-
timeLock
|
|
113
|
+
timeLock?: string;
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
export interface SecurityMetadata {
|
|
@@ -115,16 +119,13 @@ export interface SecurityMetadata {
|
|
|
115
119
|
accessControl: AccessControlInfo;
|
|
116
120
|
}
|
|
117
121
|
|
|
118
|
-
// Redemption metadata interfaces
|
|
119
|
-
export interface RedemptionExpectedTime {
|
|
120
|
-
upto1M: string;
|
|
121
|
-
upto10M: string;
|
|
122
|
-
above10M: string;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
122
|
export interface RedemptionInfo {
|
|
126
123
|
instantWithdrawalVault: InstantWithdrawalVault;
|
|
127
|
-
|
|
124
|
+
redemptionsInfo: {
|
|
125
|
+
title: string; // e.g. Upto $1M
|
|
126
|
+
description: string; // e.g. "1-2 hours"
|
|
127
|
+
}[],
|
|
128
|
+
alerts: StrategyAlert[];
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
export enum FlowChartColors {
|
|
@@ -142,7 +143,8 @@ export enum StrategyLiveStatus {
|
|
|
142
143
|
ACTIVE = "Active",
|
|
143
144
|
NEW = "New",
|
|
144
145
|
COMING_SOON = "Coming Soon",
|
|
145
|
-
|
|
146
|
+
DEPRECATED = "Deprecated", // active but not recommended
|
|
147
|
+
RETIRED = "Retired", // not active anymore
|
|
146
148
|
HOT = "Hot & New 🔥"
|
|
147
149
|
}
|
|
148
150
|
|
|
@@ -153,7 +155,6 @@ export interface StrategyAlert {
|
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
export interface StrategySettings {
|
|
156
|
-
maxTVL?: Web3Number;
|
|
157
158
|
liveStatus?: StrategyLiveStatus;
|
|
158
159
|
isPaused?: boolean;
|
|
159
160
|
isInMaintenance?: boolean;
|
|
@@ -169,6 +170,17 @@ export interface StrategySettings {
|
|
|
169
170
|
tags?: StrategyTag[];
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
export interface StrategyApyHistoryUIConfig {
|
|
174
|
+
// Defaults to true in UI if omitted.
|
|
175
|
+
showApyHistory?: boolean;
|
|
176
|
+
// Optional message shown when APY history is hidden.
|
|
177
|
+
noApyHistoryMessage?: string;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface FeeBps {
|
|
181
|
+
performanceFeeBps: number;
|
|
182
|
+
}
|
|
183
|
+
|
|
172
184
|
/**
|
|
173
185
|
* @property risk.riskFactor.factor - The risk factors that are considered for the strategy.
|
|
174
186
|
* @property risk.riskFactor.factor - The value of the risk factor from 0 to 10, 0 being the lowest and 10 being the highest.
|
|
@@ -179,6 +191,19 @@ export interface IStrategyMetadata<T> {
|
|
|
179
191
|
id: string;
|
|
180
192
|
name: string;
|
|
181
193
|
description: string | React.ReactNode;
|
|
194
|
+
/**
|
|
195
|
+
* Optional UI sort priority. Higher shows earlier.
|
|
196
|
+
* Intended for pinning flagship parent vaults (e.g. BTC above STRK).
|
|
197
|
+
*/
|
|
198
|
+
priority?: number;
|
|
199
|
+
/**
|
|
200
|
+
* Optional UI config for the variant intro popup (strategy page).
|
|
201
|
+
* Should be identical across strategies that share the same `parentId`.
|
|
202
|
+
*/
|
|
203
|
+
variantIntro?: {
|
|
204
|
+
title: string;
|
|
205
|
+
description: string;
|
|
206
|
+
};
|
|
182
207
|
address: ContractAddr;
|
|
183
208
|
launchBlock: number;
|
|
184
209
|
type: "ERC4626" | "ERC721" | "Other";
|
|
@@ -195,6 +220,8 @@ export interface IStrategyMetadata<T> {
|
|
|
195
220
|
notARisks: RiskType[];
|
|
196
221
|
};
|
|
197
222
|
apyMethodology?: string;
|
|
223
|
+
realizedApyMethodology?: string;
|
|
224
|
+
feeBps?: FeeBps;
|
|
198
225
|
additionalInfo: T;
|
|
199
226
|
contractDetails: {
|
|
200
227
|
address: ContractAddr;
|
|
@@ -205,13 +232,20 @@ export interface IStrategyMetadata<T> {
|
|
|
205
232
|
points?: {multiplier: number, logo: string, toolTip?: string}[];
|
|
206
233
|
docs?: string;
|
|
207
234
|
investmentSteps: string[];
|
|
208
|
-
curator?:
|
|
235
|
+
curator?: ICurator,
|
|
209
236
|
isPreview?: boolean;
|
|
210
|
-
category: StrategyCategory;
|
|
211
237
|
tags?: StrategyTag[];
|
|
212
238
|
security: SecurityMetadata;
|
|
213
239
|
redemptionInfo: RedemptionInfo;
|
|
240
|
+
usualTimeToEarnings: null | string; // e.g. "2 weeks" // some strats grow like step functions
|
|
241
|
+
usualTimeToEarningsDescription: null | string; // e.g. "LSTs price on DEX goes up roughly every 2 weeks"
|
|
242
|
+
discontinuationInfo?: {
|
|
243
|
+
date?: Date;
|
|
244
|
+
reason?: React.ReactNode | string;
|
|
245
|
+
info?: React.ReactNode | string;
|
|
246
|
+
};
|
|
214
247
|
settings?: StrategySettings;
|
|
248
|
+
apyHistoryUIConfig?: StrategyApyHistoryUIConfig;
|
|
215
249
|
// Legacy field for multi-step strategies (deprecated, use investmentFlows instead)
|
|
216
250
|
actions?: Array<{
|
|
217
251
|
name?: string;
|
|
@@ -224,6 +258,8 @@ export interface IStrategyMetadata<T> {
|
|
|
224
258
|
amount?: string | number;
|
|
225
259
|
isDeposit?: boolean;
|
|
226
260
|
}>;
|
|
261
|
+
parentId?: string;
|
|
262
|
+
parentName?: string;
|
|
227
263
|
}
|
|
228
264
|
|
|
229
265
|
export interface IInvestmentFlow {
|
|
@@ -250,6 +286,23 @@ export function getMainnetConfig(
|
|
|
250
286
|
};
|
|
251
287
|
}
|
|
252
288
|
|
|
289
|
+
export const getStrategyTagDesciption = (tag: StrategyTag): string => {
|
|
290
|
+
switch (tag) {
|
|
291
|
+
case StrategyTag.META_VAULT:
|
|
292
|
+
return "A meta vault is a vault that auto allocates funds to multiple vaults based on optimal yield opportunities";
|
|
293
|
+
case StrategyTag.LEVERED:
|
|
294
|
+
return "Looping vaults on Endur LSTs with leveraged borrowing of STRK or BTC to increase yield (2-4x higher yield than simply staking)";
|
|
295
|
+
case StrategyTag.AUTOMATED_LP:
|
|
296
|
+
return "Automated LP vaults on Ekubo that rebalance position automatically, ensuring you earn fees efficiently";
|
|
297
|
+
case StrategyTag.BTC:
|
|
298
|
+
return "BTC linked vaults";
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export const getAllStrategyTags = (): StrategyTag[] => {
|
|
303
|
+
return Object.values(StrategyTag);
|
|
304
|
+
}
|
|
305
|
+
|
|
253
306
|
export const getRiskExplaination = (riskType: RiskType) => {
|
|
254
307
|
switch (riskType) {
|
|
255
308
|
case RiskType.MARKET_RISK:
|
|
@@ -322,7 +375,7 @@ export function highlightTextWithLinks(
|
|
|
322
375
|
{parts.map((part, i) => {
|
|
323
376
|
const match = highlights.find(m => m.highlight.toLowerCase() === part.toLowerCase());
|
|
324
377
|
return match ? (
|
|
325
|
-
<a key={i} href={match.link} target="_blank" style={{ color: '
|
|
378
|
+
<a key={i} href={match.link} target="_blank" style={{ color: 'white', background: 'rgba(255, 255, 255, 0.04)' }}>
|
|
326
379
|
{part}
|
|
327
380
|
</a>
|
|
328
381
|
) : (
|
|
@@ -383,4 +436,9 @@ export const Protocols = {
|
|
|
383
436
|
VESU: VesuProtocol,
|
|
384
437
|
ENDUR: EndurProtocol,
|
|
385
438
|
EXTENDED: ExtendedProtocol
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export const UnwrapLabsCurator: ICurator = {
|
|
442
|
+
name: "Unwrap Labs",
|
|
443
|
+
logo: "https://assets.troves.fi/integrations/unwraplabs/white.png"
|
|
386
444
|
}
|
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,
|
package/src/modules/erc20.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
|
2
2
|
import { IConfig } from "@/interfaces";
|
|
3
3
|
import { Contract } from "starknet";
|
|
4
4
|
import ERC20Abi from '@/data/erc20.abi.json';
|
|
5
|
-
|
|
5
|
+
import { uint256 } from "starknet";
|
|
6
6
|
export class ERC20 {
|
|
7
7
|
readonly config: IConfig;
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@ export class ERC20 {
|
|
|
12
12
|
|
|
13
13
|
contract(addr: string | ContractAddr) {
|
|
14
14
|
const _addr = typeof addr === 'string' ? addr : addr.address;
|
|
15
|
-
return new Contract({abi: ERC20Abi, address: _addr, providerOrAccount: this.config.provider});
|
|
15
|
+
return new Contract({ abi: ERC20Abi, address: _addr, providerOrAccount: this.config.provider });
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
async balanceOf(token: string | ContractAddr, address: string | ContractAddr, tokenDecimals: number) {
|
|
@@ -26,4 +26,20 @@ export class ERC20 {
|
|
|
26
26
|
const allowance = await contract.call('allowance', [owner.toString(), spender.toString()]);
|
|
27
27
|
return Web3Number.fromWei(allowance.toString(), tokenDecimals);
|
|
28
28
|
}
|
|
29
|
+
|
|
30
|
+
approve(
|
|
31
|
+
token: string | ContractAddr,
|
|
32
|
+
spender: string | ContractAddr,
|
|
33
|
+
amount: Web3Number
|
|
34
|
+
) {
|
|
35
|
+
const contract = this.contract(token);
|
|
36
|
+
const amountUint256 = uint256.bnToUint256(amount.toWei());
|
|
37
|
+
const approveCall = contract.populate("approve", [
|
|
38
|
+
spender.toString(),
|
|
39
|
+
amountUint256.low.toString(),
|
|
40
|
+
amountUint256.high.toString(),
|
|
41
|
+
]);
|
|
42
|
+
return approveCall;
|
|
43
|
+
}
|
|
44
|
+
|
|
29
45
|
}
|
package/src/modules/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './pricer';
|
|
2
|
+
export * from './pricer-avnu-api';
|
|
2
3
|
export * from './pragma';
|
|
3
4
|
export * from './zkLend';
|
|
4
5
|
export * from './pricer-from-api';
|
|
@@ -6,4 +7,5 @@ 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 './ekubo-pricer'
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { TokenInfo } from "@/interfaces/common";
|
|
3
|
+
import { IConfig } from "@/interfaces/common";
|
|
4
|
+
import { PricerBase } from "./pricerBase";
|
|
5
|
+
import { PriceInfo } from "./pricer";
|
|
6
|
+
import { logger } from "@/utils/logger";
|
|
7
|
+
import { ContractAddr } from "@/dataTypes";
|
|
8
|
+
|
|
9
|
+
const AVNU_TOKENS_API = "https://starknet.impulse.avnu.fi/v3/tokens";
|
|
10
|
+
|
|
11
|
+
interface AvnuTokenApiEntry {
|
|
12
|
+
address: string;
|
|
13
|
+
symbol: string;
|
|
14
|
+
starknet?: {
|
|
15
|
+
usd?: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Polls Avnu impulse tokens API and keeps USD prices in memory for configured tokens.
|
|
21
|
+
* Price timestamp is set when each poll request completes.
|
|
22
|
+
*/
|
|
23
|
+
export class PricerAvnuApi extends PricerBase {
|
|
24
|
+
protected prices: { [key: string]: PriceInfo } = {};
|
|
25
|
+
|
|
26
|
+
readonly refreshInterval = 15_000;
|
|
27
|
+
readonly staleTime = 5 * 60 * 1000;
|
|
28
|
+
|
|
29
|
+
private pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
30
|
+
private loading = false;
|
|
31
|
+
|
|
32
|
+
constructor(config: IConfig, tokens: TokenInfo[]) {
|
|
33
|
+
super(config, tokens);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
start() {
|
|
37
|
+
this._loadPrices();
|
|
38
|
+
this.pollTimer = setInterval(() => {
|
|
39
|
+
this._loadPrices();
|
|
40
|
+
}, this.refreshInterval);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
stop() {
|
|
44
|
+
if (this.pollTimer) {
|
|
45
|
+
clearInterval(this.pollTimer);
|
|
46
|
+
this.pollTimer = null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
isStale(timestamp: Date) {
|
|
51
|
+
return Date.now() - timestamp.getTime() > this.staleTime;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
hasPrice(tokenSymbol: string) {
|
|
55
|
+
const info = this.prices[tokenSymbol];
|
|
56
|
+
return !!info && !this.isStale(info.timestamp);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async getPrice(tokenSymbol: string): Promise<PriceInfo> {
|
|
60
|
+
const info = this.prices[tokenSymbol];
|
|
61
|
+
if (!info) {
|
|
62
|
+
throw new Error(`AvnuApi: price of ${tokenSymbol} not found`);
|
|
63
|
+
}
|
|
64
|
+
if (this.isStale(info.timestamp)) {
|
|
65
|
+
throw new Error(`AvnuApi: price of ${tokenSymbol} is stale`);
|
|
66
|
+
}
|
|
67
|
+
return info;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
protected async _loadPrices() {
|
|
71
|
+
if (this.loading) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.loading = true;
|
|
75
|
+
const timestamp = new Date();
|
|
76
|
+
try {
|
|
77
|
+
const result = await axios.get<AvnuTokenApiEntry[]>(AVNU_TOKENS_API);
|
|
78
|
+
const priceByAddress = new Map<string, number>();
|
|
79
|
+
for (const entry of result.data) {
|
|
80
|
+
const usd = entry.starknet?.usd;
|
|
81
|
+
if (usd != null && usd > 0) {
|
|
82
|
+
priceByAddress.set(ContractAddr.standardise(entry.address), usd);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const token of this.tokens) {
|
|
87
|
+
if (token.symbol === "USDT" || token.symbol === "USDC") {
|
|
88
|
+
this.prices[token.symbol] = { price: 1, timestamp };
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const targetToken = token.priceProxySymbol
|
|
93
|
+
? this.tokens.find((t) => t.symbol === token.priceProxySymbol)
|
|
94
|
+
: token;
|
|
95
|
+
if (!targetToken) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const addr = targetToken.address.address;
|
|
100
|
+
const price = priceByAddress.get(addr);
|
|
101
|
+
if (price != null) {
|
|
102
|
+
this.prices[token.symbol] = { price, timestamp };
|
|
103
|
+
logger.verbose(
|
|
104
|
+
`AvnuApi: ${token.symbol} -> $${price}`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch (error: any) {
|
|
109
|
+
logger.warn(`AvnuApi: failed to fetch tokens: ${error?.message ?? error}`);
|
|
110
|
+
} finally {
|
|
111
|
+
this.loading = false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
package/src/modules/pricer.ts
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
import { FatalError, Global } from "@/global";
|
|
3
|
-
import { TokenInfo } from "@/interfaces/common";
|
|
3
|
+
import { PriceMethod, TokenInfo } from "@/interfaces/common";
|
|
4
4
|
import { IConfig } from "@/interfaces/common";
|
|
5
5
|
import { Web3Number } from "@/dataTypes";
|
|
6
6
|
import { PricerBase } from "./pricerBase";
|
|
7
7
|
import { logger } from "@/utils/logger";
|
|
8
8
|
import { AvnuWrapper } from "./avnu";
|
|
9
9
|
import { BlockIdentifier } from "starknet";
|
|
10
|
+
import { PricerAvnuApi } from "./pricer-avnu-api";
|
|
10
11
|
|
|
11
12
|
export interface PriceInfo {
|
|
12
13
|
price: number,
|
|
13
14
|
timestamp: Date
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
const PRICE_METHOD_PRIORITY: PriceMethod[] = [
|
|
18
|
+
'AvnuApi',
|
|
19
|
+
'Coinbase',
|
|
20
|
+
'Coinmarketcap',
|
|
21
|
+
'Ekubo',
|
|
22
|
+
'Avnu',
|
|
23
|
+
];
|
|
24
|
+
|
|
16
25
|
export class Pricer extends PricerBase {
|
|
17
26
|
protected prices: {
|
|
18
27
|
[key: string]: PriceInfo
|
|
@@ -21,9 +30,11 @@ export class Pricer extends PricerBase {
|
|
|
21
30
|
refreshInterval = 30000;
|
|
22
31
|
staleTime = 60000;
|
|
23
32
|
|
|
33
|
+
protected readonly avnuApiPricer: PricerAvnuApi;
|
|
34
|
+
|
|
24
35
|
// code populates this map during runtime to determine which method to use for a given token
|
|
25
36
|
// The method set will be the first one to try after first attempt
|
|
26
|
-
protected methodToUse: {[tokenSymbol: string]:
|
|
37
|
+
protected methodToUse: {[tokenSymbol: string]: PriceMethod} = {};
|
|
27
38
|
|
|
28
39
|
/**
|
|
29
40
|
* TOKENA and TOKENB are the two token names to get price of TokenA in terms of TokenB
|
|
@@ -36,6 +47,7 @@ export class Pricer extends PricerBase {
|
|
|
36
47
|
super(config, tokens);
|
|
37
48
|
this.refreshInterval = refreshInterval;
|
|
38
49
|
this.staleTime = staleTime;
|
|
50
|
+
this.avnuApiPricer = new PricerAvnuApi(config, tokens);
|
|
39
51
|
}
|
|
40
52
|
|
|
41
53
|
isReady() {
|
|
@@ -69,6 +81,7 @@ export class Pricer extends PricerBase {
|
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
start() {
|
|
84
|
+
this.avnuApiPricer.start();
|
|
72
85
|
this._loadPrices();
|
|
73
86
|
setInterval(() => {
|
|
74
87
|
this._loadPrices();
|
|
@@ -96,6 +109,9 @@ export class Pricer extends PricerBase {
|
|
|
96
109
|
let retry = 0;
|
|
97
110
|
while (retry < MAX_RETRIES) {
|
|
98
111
|
try {
|
|
112
|
+
if (token.dontPrice) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
99
115
|
if (token.symbol === 'USDT' || token.symbol === 'USDC') {
|
|
100
116
|
this.prices[token.symbol] = {
|
|
101
117
|
price: 1,
|
|
@@ -145,56 +161,70 @@ export class Pricer extends PricerBase {
|
|
|
145
161
|
}
|
|
146
162
|
}
|
|
147
163
|
|
|
148
|
-
async _getPrice(token: TokenInfo
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
164
|
+
async _getPrice(token: TokenInfo): Promise<number> {
|
|
165
|
+
const methodsToTry = this._getMethodsToTry(token);
|
|
166
|
+
|
|
167
|
+
for (const method of methodsToTry) {
|
|
168
|
+
logger.verbose(`Fetching price of ${token.symbol} using ${method}`);
|
|
169
|
+
try {
|
|
170
|
+
const result = await this._tryPriceMethod(token, method);
|
|
171
|
+
if (!token.priceMethod) {
|
|
172
|
+
this.methodToUse[token.symbol] = method;
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
} catch (error: any) {
|
|
176
|
+
console.warn(`${method}: price err [${token.symbol}]: `, error.message);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
throw new FatalError(`Price not found for ${token.symbol}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
protected _getMethodsToTry(token: TokenInfo): PriceMethod[] {
|
|
184
|
+
const methods: PriceMethod[] = [];
|
|
185
|
+
|
|
186
|
+
if (token.priceMethod) {
|
|
187
|
+
methods.push(token.priceMethod);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const pinned = this.methodToUse[token.symbol];
|
|
191
|
+
if (pinned && pinned !== token.priceMethod) {
|
|
192
|
+
methods.push(pinned);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
for (const method of PRICE_METHOD_PRIORITY) {
|
|
196
|
+
if (!methods.includes(method)) {
|
|
197
|
+
methods.push(method);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return methods;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
protected async _tryPriceMethod(token: TokenInfo, method: PriceMethod): Promise<number> {
|
|
205
|
+
switch (method) {
|
|
206
|
+
case 'AvnuApi':
|
|
207
|
+
return await this._getPriceAvnuApi(token);
|
|
152
208
|
case 'Coinbase':
|
|
153
|
-
|
|
154
|
-
// const result = await this._getPriceCoinbase(token);
|
|
155
|
-
// this.methodToUse[token.symbol] = 'Coinbase';
|
|
156
|
-
// return result;
|
|
157
|
-
// } catch (error: any) {
|
|
158
|
-
// console.warn(`Coinbase: price err: message [${token.symbol}]: `, error.message);
|
|
159
|
-
// // do nothing, try next
|
|
160
|
-
// }
|
|
209
|
+
return await this._getPriceCoinbase(token);
|
|
161
210
|
case 'Coinmarketcap':
|
|
162
|
-
|
|
163
|
-
const result = await this._getPriceCoinMarketCap(token);
|
|
164
|
-
this.methodToUse[token.symbol] = 'Coinmarketcap';
|
|
165
|
-
return result;
|
|
166
|
-
} catch (error: any) {
|
|
167
|
-
console.warn(`CoinMarketCap: price err [${token.symbol}]: `, Object.keys(error));
|
|
168
|
-
console.warn(`CoinMarketCap: price err [${token.symbol}]: `, error.message);
|
|
169
|
-
}
|
|
211
|
+
return await this._getPriceCoinMarketCap(token);
|
|
170
212
|
case 'Ekubo':
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
} catch (error: any) {
|
|
176
|
-
console.warn(`Ekubo: price err [${token.symbol}]: `, error.message);
|
|
177
|
-
console.warn(`Ekubo: price err [${token.symbol}]: `, Object.keys(error));
|
|
178
|
-
// do nothing, try next
|
|
179
|
-
}
|
|
213
|
+
return await this._getPriceEkubo(
|
|
214
|
+
token,
|
|
215
|
+
new Web3Number(token.priceCheckAmount ? token.priceCheckAmount : 1, token.decimals),
|
|
216
|
+
);
|
|
180
217
|
case 'Avnu':
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
} catch (error: any) {
|
|
186
|
-
console.warn(`Avnu: price err [${token.symbol}]: `, error.message);
|
|
187
|
-
console.warn(`Avnu: price err [${token.symbol}]: `, Object.keys(error));
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// if methodToUse is the default one, pass Coinbase to try all from start
|
|
192
|
-
if (defaultMethod == 'all') {
|
|
193
|
-
// try again with coinbase
|
|
194
|
-
return await this._getPrice(token, 'Coinbase');
|
|
218
|
+
return await this._getAvnuPrice(
|
|
219
|
+
token,
|
|
220
|
+
new Web3Number(token.priceCheckAmount ? token.priceCheckAmount : 1, token.decimals),
|
|
221
|
+
);
|
|
195
222
|
}
|
|
223
|
+
}
|
|
196
224
|
|
|
197
|
-
|
|
225
|
+
async _getPriceAvnuApi(token: TokenInfo): Promise<number> {
|
|
226
|
+
const priceInfo = await this.avnuApiPricer.getPrice(token.symbol);
|
|
227
|
+
return priceInfo.price;
|
|
198
228
|
}
|
|
199
229
|
|
|
200
230
|
async _getPriceCoinbase(token: TokenInfo) {
|
package/src/node/pricer-redis.ts
CHANGED
|
@@ -18,6 +18,7 @@ export class PricerRedis extends Pricer {
|
|
|
18
18
|
await this.initRedis(redisUrl);
|
|
19
19
|
|
|
20
20
|
logger.info(`Starting Pricer with Redis`);
|
|
21
|
+
this.avnuApiPricer.start();
|
|
21
22
|
this._loadPrices(this._setRedisPrices.bind(this));
|
|
22
23
|
setInterval(() => {
|
|
23
24
|
this._loadPrices(this._setRedisPrices.bind(this));
|