@basedone/core 0.1.10 → 0.2.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.
- package/dist/chunk-4UEJOM6W.mjs +1 -3
- package/dist/chunk-MVFO4WRF.mjs +2091 -0
- package/dist/chunk-VBC6EQ7Q.mjs +235 -0
- package/dist/client-CgmiTuEX.d.mts +179 -0
- package/dist/client-CgmiTuEX.d.ts +179 -0
- package/dist/ecommerce.d.mts +3986 -0
- package/dist/ecommerce.d.ts +3986 -0
- package/dist/ecommerce.js +2135 -0
- package/dist/ecommerce.mjs +2 -0
- package/dist/index.d.mts +51 -43
- package/dist/index.d.ts +51 -43
- package/dist/index.js +2795 -205
- package/dist/index.mjs +68 -90
- package/dist/{meta-FVJIMALT.mjs → meta-JB5ITE27.mjs} +4 -10
- package/dist/meta-UOGUG3OW.mjs +3 -7
- package/dist/{perpDexs-GGL32HT4.mjs → perpDexs-3LRJ5ZHM.mjs} +37 -8
- package/dist/{perpDexs-G7V2QIM6.mjs → perpDexs-4ISLD7NX.mjs} +177 -32
- package/dist/react.d.mts +39 -0
- package/dist/react.d.ts +39 -0
- package/dist/react.js +268 -0
- package/dist/react.mjs +31 -0
- package/dist/{spotMeta-OD7S6HGW.mjs → spotMeta-GHXX7C5M.mjs} +24 -9
- package/dist/{spotMeta-PCN4Z4R3.mjs → spotMeta-IBBUP2SG.mjs} +54 -6
- package/dist/staticMeta-GM7T3OYL.mjs +3 -6
- package/dist/staticMeta-QV2KMX57.mjs +3 -6
- package/ecommerce.ts +15 -0
- package/index.ts +6 -0
- package/lib/ecommerce/FLASH_SALES.md +340 -0
- package/lib/ecommerce/QUICK_REFERENCE.md +211 -0
- package/lib/ecommerce/README.md +391 -0
- package/lib/ecommerce/USAGE_EXAMPLES.md +704 -0
- package/lib/ecommerce/client/base.ts +272 -0
- package/lib/ecommerce/client/customer.ts +639 -0
- package/lib/ecommerce/client/merchant.ts +1341 -0
- package/lib/ecommerce/index.ts +51 -0
- package/lib/ecommerce/types/entities.ts +791 -0
- package/lib/ecommerce/types/enums.ts +270 -0
- package/lib/ecommerce/types/index.ts +18 -0
- package/lib/ecommerce/types/requests.ts +580 -0
- package/lib/ecommerce/types/responses.ts +857 -0
- package/lib/ecommerce/utils/errors.ts +113 -0
- package/lib/ecommerce/utils/helpers.ts +131 -0
- package/lib/hip3/market-info.ts +1 -1
- package/lib/instrument/client.ts +351 -0
- package/lib/meta/data/mainnet/perpDexs.json +34 -4
- package/lib/meta/data/mainnet/spotMeta.json +21 -3
- package/lib/meta/data/testnet/meta.json +1 -3
- package/lib/meta/data/testnet/perpDexs.json +174 -28
- package/lib/meta/data/testnet/spotMeta.json +51 -0
- package/lib/react/InstrumentProvider.tsx +69 -0
- package/lib/utils/flooredDateTime.ts +55 -0
- package/lib/utils/time.ts +51 -0
- package/package.json +37 -11
- package/react.ts +1 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ecommerce API Error Utilities
|
|
3
|
+
*
|
|
4
|
+
* This module contains error handling utilities for the ecommerce API.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Ecommerce API error class
|
|
9
|
+
*/
|
|
10
|
+
export class EcommerceApiError extends Error {
|
|
11
|
+
/** HTTP status code */
|
|
12
|
+
public statusCode?: number;
|
|
13
|
+
|
|
14
|
+
/** Response data */
|
|
15
|
+
public data?: any;
|
|
16
|
+
|
|
17
|
+
/** Is network error */
|
|
18
|
+
public isNetworkError: boolean;
|
|
19
|
+
|
|
20
|
+
/** Is timeout error */
|
|
21
|
+
public isTimeoutError: boolean;
|
|
22
|
+
|
|
23
|
+
/** Is authentication error */
|
|
24
|
+
public isAuthError: boolean;
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
message: string,
|
|
28
|
+
statusCode?: number,
|
|
29
|
+
data?: any,
|
|
30
|
+
isNetworkError = false,
|
|
31
|
+
isTimeoutError = false
|
|
32
|
+
) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "EcommerceApiError";
|
|
35
|
+
this.statusCode = statusCode;
|
|
36
|
+
this.data = data;
|
|
37
|
+
this.isNetworkError = isNetworkError;
|
|
38
|
+
this.isTimeoutError = isTimeoutError;
|
|
39
|
+
this.isAuthError = statusCode === 401 || statusCode === 403;
|
|
40
|
+
|
|
41
|
+
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
42
|
+
if (Error.captureStackTrace) {
|
|
43
|
+
Error.captureStackTrace(this, EcommerceApiError);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parse error from axios error or API response
|
|
50
|
+
*/
|
|
51
|
+
export function parseError(error: any): EcommerceApiError {
|
|
52
|
+
// Network error
|
|
53
|
+
if (error.code === "ECONNABORTED" || error.code === "ETIMEDOUT") {
|
|
54
|
+
return new EcommerceApiError(
|
|
55
|
+
"Request timeout",
|
|
56
|
+
undefined,
|
|
57
|
+
undefined,
|
|
58
|
+
false,
|
|
59
|
+
true
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (error.code === "ERR_NETWORK" || !error.response) {
|
|
64
|
+
return new EcommerceApiError(
|
|
65
|
+
error.message || "Network error",
|
|
66
|
+
undefined,
|
|
67
|
+
undefined,
|
|
68
|
+
true,
|
|
69
|
+
false
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// API error
|
|
74
|
+
const response = error.response;
|
|
75
|
+
const statusCode = response?.status;
|
|
76
|
+
const data = response?.data;
|
|
77
|
+
|
|
78
|
+
const message =
|
|
79
|
+
data?.error ||
|
|
80
|
+
data?.message ||
|
|
81
|
+
error.message ||
|
|
82
|
+
`API error (${statusCode})`;
|
|
83
|
+
|
|
84
|
+
return new EcommerceApiError(message, statusCode, data);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Check if error is retryable
|
|
89
|
+
*/
|
|
90
|
+
export function isRetryableError(error: EcommerceApiError): boolean {
|
|
91
|
+
// Retry on network errors
|
|
92
|
+
if (error.isNetworkError) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Retry on timeout
|
|
97
|
+
if (error.isTimeoutError) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Retry on 5xx errors
|
|
102
|
+
if (error.statusCode && error.statusCode >= 500) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Retry on 429 (rate limit)
|
|
107
|
+
if (error.statusCode === 429) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ecommerce API Helper Utilities
|
|
3
|
+
*
|
|
4
|
+
* This module contains helper functions for the ecommerce API.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Build query string from params object
|
|
9
|
+
*/
|
|
10
|
+
export function buildQueryString(params: Record<string, any>): string {
|
|
11
|
+
const searchParams = new URLSearchParams();
|
|
12
|
+
|
|
13
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
14
|
+
if (value !== undefined && value !== null) {
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
// Handle arrays (e.g., ids, tags)
|
|
17
|
+
searchParams.append(key, value.join(","));
|
|
18
|
+
} else {
|
|
19
|
+
searchParams.append(key, String(value));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const queryString = searchParams.toString();
|
|
25
|
+
return queryString ? `?${queryString}` : "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Sleep for specified milliseconds
|
|
30
|
+
*/
|
|
31
|
+
export function sleep(ms: number): Promise<void> {
|
|
32
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Calculate exponential backoff delay
|
|
37
|
+
*/
|
|
38
|
+
export function getBackoffDelay(attempt: number, baseDelay = 1000): number {
|
|
39
|
+
return Math.min(baseDelay * Math.pow(2, attempt), 30000);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Retry function with exponential backoff
|
|
44
|
+
*/
|
|
45
|
+
export async function retryWithBackoff<T>(
|
|
46
|
+
fn: () => Promise<T>,
|
|
47
|
+
maxRetries = 3,
|
|
48
|
+
baseDelay = 1000,
|
|
49
|
+
shouldRetry?: (error: any) => boolean
|
|
50
|
+
): Promise<T> {
|
|
51
|
+
let lastError: any;
|
|
52
|
+
|
|
53
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
54
|
+
try {
|
|
55
|
+
return await fn();
|
|
56
|
+
} catch (error) {
|
|
57
|
+
lastError = error;
|
|
58
|
+
|
|
59
|
+
// Check if we should retry
|
|
60
|
+
if (attempt < maxRetries && (!shouldRetry || shouldRetry(error))) {
|
|
61
|
+
const delay = getBackoffDelay(attempt, baseDelay);
|
|
62
|
+
await sleep(delay);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
throw lastError;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Format price for display
|
|
75
|
+
*/
|
|
76
|
+
export function formatPrice(price: string | number, decimals = 2): string {
|
|
77
|
+
const num = typeof price === "string" ? parseFloat(price) : price;
|
|
78
|
+
return num.toFixed(decimals);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Validate email address
|
|
83
|
+
*/
|
|
84
|
+
export function isValidEmail(email: string): boolean {
|
|
85
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
86
|
+
return emailRegex.test(email);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Validate Ethereum address
|
|
91
|
+
*/
|
|
92
|
+
export function isValidAddress(address: string): boolean {
|
|
93
|
+
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Truncate address for display
|
|
98
|
+
*/
|
|
99
|
+
export function truncateAddress(address: string, start = 6, end = 4): string {
|
|
100
|
+
if (!address || address.length < start + end) {
|
|
101
|
+
return address;
|
|
102
|
+
}
|
|
103
|
+
return `${address.slice(0, start)}...${address.slice(-end)}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Calculate discount amount
|
|
108
|
+
*/
|
|
109
|
+
export function calculateDiscountAmount(
|
|
110
|
+
price: number,
|
|
111
|
+
discountType: "PERCENTAGE" | "FIXED_AMOUNT",
|
|
112
|
+
discountValue: number
|
|
113
|
+
): number {
|
|
114
|
+
if (discountType === "PERCENTAGE") {
|
|
115
|
+
return (price * discountValue) / 100;
|
|
116
|
+
}
|
|
117
|
+
return Math.min(discountValue, price);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Calculate final price after discount
|
|
122
|
+
*/
|
|
123
|
+
export function calculateFinalPrice(
|
|
124
|
+
price: number,
|
|
125
|
+
discountType: "PERCENTAGE" | "FIXED_AMOUNT",
|
|
126
|
+
discountValue: number
|
|
127
|
+
): number {
|
|
128
|
+
const discountAmount = calculateDiscountAmount(price, discountType, discountValue);
|
|
129
|
+
return Math.max(0, price - discountAmount);
|
|
130
|
+
}
|
|
131
|
+
|
package/lib/hip3/market-info.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface PerpsUniverse {
|
|
|
29
29
|
/** Indicates if the universe is delisted. */
|
|
30
30
|
isDelisted?: true;
|
|
31
31
|
/** Indicates if the universe is in growth mode, eligible for discounted fees */
|
|
32
|
-
growthMode?:
|
|
32
|
+
growthMode?: "enabled";
|
|
33
33
|
/** Margin mode for the universe. */
|
|
34
34
|
marginMode?: "strictIsolated" | "noCross";
|
|
35
35
|
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { SpotMeta, SpotToken } from "@nktkas/hyperliquid";
|
|
2
|
+
import { AllPerpsMeta, PerpsMeta } from "../hip3/market-info";
|
|
3
|
+
|
|
4
|
+
export interface BaseInstrument {
|
|
5
|
+
assetId: number;
|
|
6
|
+
symbol: string; // Display name (e.g., "BTC/USDC" for spot, "BTC" for perps)
|
|
7
|
+
coin: string; // Internal name used by Hyperliquid API (e.g., "@107" for spot, "BTC" for perps)
|
|
8
|
+
szDecimals: number; // Size decimals for formatting
|
|
9
|
+
type: "spot" | "futures";
|
|
10
|
+
isDelisted?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Futures-specific types (metadata only - no asset context data)
|
|
14
|
+
export interface PerpsInstrument extends BaseInstrument {
|
|
15
|
+
type: "futures";
|
|
16
|
+
leverage: number;
|
|
17
|
+
collateralTokenSymbol: string;
|
|
18
|
+
dex?: string; // HIP3 dex name (e.g., "xyz" for "xyz:MSTR")
|
|
19
|
+
collateralTokenIndex?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Spot-specific types (metadata only - no asset context data)
|
|
23
|
+
export interface SpotInstrument extends BaseInstrument {
|
|
24
|
+
type: "spot";
|
|
25
|
+
baseToken: SpotToken;
|
|
26
|
+
quoteToken: SpotToken;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type MarketInstrument = PerpsInstrument | SpotInstrument;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Asset ID calculation utilities
|
|
33
|
+
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/asset-ids
|
|
34
|
+
*/
|
|
35
|
+
export const AssetIdUtils = {
|
|
36
|
+
/**
|
|
37
|
+
* Calculate perp asset ID
|
|
38
|
+
* - Root dex: assetIndex (0, 1, 2, ...)
|
|
39
|
+
* - HIP3 dex: 100000 + dexIndex * 10000 + assetIndex
|
|
40
|
+
*/
|
|
41
|
+
calcPerpAssetId(dexIndex: number, assetIndex: number): number {
|
|
42
|
+
if (dexIndex === 0) return assetIndex;
|
|
43
|
+
return 100000 + dexIndex * 10000 + assetIndex;
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Calculate spot asset ID: 10000 + pairIndex
|
|
48
|
+
*/
|
|
49
|
+
calcSpotAssetId(pairIndex: number): number {
|
|
50
|
+
return 10000 + pairIndex;
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check if symbol is HIP3 format (contains ":")
|
|
55
|
+
*/
|
|
56
|
+
isHip3Symbol(symbol: string): boolean {
|
|
57
|
+
return symbol?.includes(":") ?? false;
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Extract dex name from HIP3 symbol (e.g., "xyz:MSTR" -> "xyz")
|
|
62
|
+
*/
|
|
63
|
+
extractDexName(symbol: string): string | undefined {
|
|
64
|
+
if (!this.isHip3Symbol(symbol)) return undefined;
|
|
65
|
+
return symbol.split(":")[0];
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Hyperliquid Instrument Client
|
|
71
|
+
*
|
|
72
|
+
* Processes spotMeta and allPerpsMeta into a unified instrument registry
|
|
73
|
+
* with O(1) lookups by symbol, coin, or assetId.
|
|
74
|
+
*
|
|
75
|
+
* Usage:
|
|
76
|
+
* ```ts
|
|
77
|
+
* const client = new InstrumentClient(spotMeta, allPerpsMeta).init();
|
|
78
|
+
* const btc = client.getInstrument("BTC");
|
|
79
|
+
* const spot = client.getInstrumentByAssetId(10001);
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export class InstrumentClient {
|
|
83
|
+
private instruments: MarketInstrument[] = [];
|
|
84
|
+
|
|
85
|
+
// O(1) lookup maps for efficient retrieval
|
|
86
|
+
private instrumentsBySymbol = new Map<string, MarketInstrument>();
|
|
87
|
+
private instrumentsByCoin = new Map<string, MarketInstrument>();
|
|
88
|
+
private instrumentsByAssetId = new Map<number, MarketInstrument>();
|
|
89
|
+
|
|
90
|
+
// Additional spot lookups (base token -> instrument)
|
|
91
|
+
private spotByBaseToken = new Map<string, SpotInstrument>();
|
|
92
|
+
|
|
93
|
+
// Dex metadata map (dex name -> PerpsMeta)
|
|
94
|
+
// Key: "" for root dex, dex name for HIP3 (e.g., "xyz")
|
|
95
|
+
private dexMetaMap = new Map<string, PerpsMeta>();
|
|
96
|
+
|
|
97
|
+
// Token index lookup for spot
|
|
98
|
+
private spotTokensByIndex = new Map<number, SpotToken>();
|
|
99
|
+
|
|
100
|
+
constructor(
|
|
101
|
+
public spotMeta: SpotMeta,
|
|
102
|
+
public allPerpsMeta: AllPerpsMeta,
|
|
103
|
+
) {}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Initialize the client by processing meta into instruments.
|
|
107
|
+
* Call this after construction to populate the instrument registry.
|
|
108
|
+
* @returns this for chaining
|
|
109
|
+
*/
|
|
110
|
+
init(): this {
|
|
111
|
+
if (!this.spotMeta || !this.allPerpsMeta) {
|
|
112
|
+
throw new Error("SpotMeta and allPerpsMeta are required");
|
|
113
|
+
}
|
|
114
|
+
// Pre-index spot tokens for O(1) lookup
|
|
115
|
+
this.indexSpotTokens();
|
|
116
|
+
|
|
117
|
+
// Process instruments
|
|
118
|
+
this.processPerpsInstruments();
|
|
119
|
+
this.processSpotInstruments();
|
|
120
|
+
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ===== Perps Meta Accessors =====
|
|
125
|
+
|
|
126
|
+
getAllPerpsMeta(): AllPerpsMeta {
|
|
127
|
+
return this.allPerpsMeta;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get PerpsMeta for a specific dex
|
|
132
|
+
* @param dex - Dex name ("" for root dex, or HIP3 dex name)
|
|
133
|
+
*/
|
|
134
|
+
getPerpDex(dex: string): PerpsMeta | undefined {
|
|
135
|
+
return this.dexMetaMap.get(dex);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get PerpsMeta by dex index in allPerpsMeta array
|
|
140
|
+
*/
|
|
141
|
+
getPerpDexByIndex(index: number): PerpsMeta | undefined {
|
|
142
|
+
return this.allPerpsMeta[index];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ===== Instrument Accessors =====
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get all instruments
|
|
149
|
+
*/
|
|
150
|
+
getAllInstruments(): MarketInstrument[] {
|
|
151
|
+
return this.instruments;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get all perps instruments
|
|
156
|
+
*/
|
|
157
|
+
getPerpsInstruments(): PerpsInstrument[] {
|
|
158
|
+
return this.instruments.filter(
|
|
159
|
+
(i): i is PerpsInstrument => i.type === "futures",
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
getHip3Instruments(): PerpsInstrument[] {
|
|
164
|
+
return this.instruments.filter(
|
|
165
|
+
(i): i is PerpsInstrument => i.type === "futures" && !!i.dex,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get all spot instruments
|
|
171
|
+
*/
|
|
172
|
+
getSpotInstruments(): SpotInstrument[] {
|
|
173
|
+
return this.instruments.filter(
|
|
174
|
+
(i): i is SpotInstrument => i.type === "spot",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get instrument by symbol or coin (tries both)
|
|
180
|
+
* @param coinOrSymbol - Symbol (e.g., "BTC", "BTC/USDC") or coin (e.g., "@107")
|
|
181
|
+
*/
|
|
182
|
+
getInstrument(coinOrSymbol: string): MarketInstrument | undefined {
|
|
183
|
+
return (
|
|
184
|
+
this.instrumentsByCoin.get(coinOrSymbol) ??
|
|
185
|
+
this.instrumentsBySymbol.get(coinOrSymbol)
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get instrument by display symbol
|
|
191
|
+
* @param symbol - Display symbol (e.g., "BTC", "BTC/USDC", "xyz:MSTR")
|
|
192
|
+
*/
|
|
193
|
+
getInstrumentBySymbol(symbol: string): MarketInstrument | undefined {
|
|
194
|
+
return this.instrumentsBySymbol.get(symbol);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get instrument by Hyperliquid API coin format
|
|
199
|
+
* @param coin - API coin name (e.g., "BTC", "@107", "xyz:MSTR")
|
|
200
|
+
*/
|
|
201
|
+
getInstrumentByCoin(coin: string): MarketInstrument | undefined {
|
|
202
|
+
return this.instrumentsByCoin.get(coin);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get instrument by asset ID
|
|
207
|
+
* @param assetId - Numeric asset ID (e.g., 1 for perps, 10001 for spot)
|
|
208
|
+
*/
|
|
209
|
+
getInstrumentByAssetId(assetId: number): MarketInstrument | undefined {
|
|
210
|
+
return this.instrumentsByAssetId.get(assetId);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get spot instrument by base token symbol
|
|
215
|
+
* @param baseSymbol - Base token symbol (e.g., "HYPE" to find "HYPE/USDC")
|
|
216
|
+
*/
|
|
217
|
+
getSpotInstrumentByBaseToken(baseSymbol: string): SpotInstrument | undefined {
|
|
218
|
+
return this.spotByBaseToken.get(baseSymbol);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Search instruments by symbol, coin, or assetId */
|
|
222
|
+
searchInstruments(
|
|
223
|
+
query: string,
|
|
224
|
+
type?: "spot" | "futures",
|
|
225
|
+
): MarketInstrument[] {
|
|
226
|
+
return this.instruments.filter((i) => {
|
|
227
|
+
if (type && i.type !== type) return false;
|
|
228
|
+
return (
|
|
229
|
+
i.symbol.toLowerCase().includes(query.toLowerCase()) ||
|
|
230
|
+
i.coin.toLowerCase().includes(query.toLowerCase())
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get spot token by index
|
|
237
|
+
*/
|
|
238
|
+
getSpotToken(index: number): SpotToken | undefined {
|
|
239
|
+
return this.spotTokensByIndex.get(index);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ===== Internal Processing =====
|
|
243
|
+
|
|
244
|
+
private indexSpotTokens(): void {
|
|
245
|
+
for (const token of this.spotMeta.tokens) {
|
|
246
|
+
this.spotTokensByIndex.set(token.index, token);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private processPerpsInstruments(): void {
|
|
251
|
+
for (let dexIndex = 0; dexIndex < this.allPerpsMeta.length; dexIndex++) {
|
|
252
|
+
const perpMeta = this.allPerpsMeta[dexIndex];
|
|
253
|
+
const dexName = this.extractDexNameFromMeta(perpMeta, dexIndex);
|
|
254
|
+
|
|
255
|
+
// Store dex meta for later lookup
|
|
256
|
+
this.dexMetaMap.set(dexName, perpMeta);
|
|
257
|
+
|
|
258
|
+
// Get collateral token symbol for this dex
|
|
259
|
+
const collateralToken = this.spotTokensByIndex.get(
|
|
260
|
+
perpMeta.collateralToken,
|
|
261
|
+
);
|
|
262
|
+
const collateralTokenSymbol = collateralToken?.name ?? "USDC";
|
|
263
|
+
|
|
264
|
+
for (
|
|
265
|
+
let assetIndex = 0;
|
|
266
|
+
assetIndex < perpMeta.universe.length;
|
|
267
|
+
assetIndex++
|
|
268
|
+
) {
|
|
269
|
+
const info = perpMeta.universe[assetIndex];
|
|
270
|
+
const assetId = AssetIdUtils.calcPerpAssetId(dexIndex, assetIndex);
|
|
271
|
+
|
|
272
|
+
const instrument: PerpsInstrument = {
|
|
273
|
+
assetId,
|
|
274
|
+
symbol: info.name,
|
|
275
|
+
coin: info.name,
|
|
276
|
+
szDecimals: info.szDecimals,
|
|
277
|
+
leverage: info.maxLeverage,
|
|
278
|
+
collateralTokenSymbol,
|
|
279
|
+
type: "futures",
|
|
280
|
+
isDelisted: info.isDelisted,
|
|
281
|
+
dex: AssetIdUtils.extractDexName(info.name),
|
|
282
|
+
collateralTokenIndex: perpMeta.collateralToken,
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
this.addInstrument(instrument);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private processSpotInstruments(): void {
|
|
291
|
+
const seenSymbols = new Set<string>();
|
|
292
|
+
|
|
293
|
+
for (const pair of this.spotMeta.universe) {
|
|
294
|
+
const [baseTokenIndex, quoteTokenIndex] = pair.tokens;
|
|
295
|
+
|
|
296
|
+
// O(1) token lookup using pre-indexed map
|
|
297
|
+
const baseToken = this.spotTokensByIndex.get(baseTokenIndex);
|
|
298
|
+
const quoteToken = this.spotTokensByIndex.get(quoteTokenIndex);
|
|
299
|
+
|
|
300
|
+
if (!baseToken || !quoteToken) continue;
|
|
301
|
+
|
|
302
|
+
const symbol = `${baseToken.name}/${quoteToken.name}`;
|
|
303
|
+
|
|
304
|
+
// Skip duplicate display symbols
|
|
305
|
+
if (seenSymbols.has(symbol)) continue;
|
|
306
|
+
seenSymbols.add(symbol);
|
|
307
|
+
|
|
308
|
+
const assetId = AssetIdUtils.calcSpotAssetId(pair.index);
|
|
309
|
+
|
|
310
|
+
const instrument: SpotInstrument = {
|
|
311
|
+
assetId,
|
|
312
|
+
symbol,
|
|
313
|
+
coin: pair.name, // e.g., "@107"
|
|
314
|
+
szDecimals: baseToken.szDecimals,
|
|
315
|
+
type: "spot",
|
|
316
|
+
baseToken,
|
|
317
|
+
quoteToken,
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
this.addInstrument(instrument);
|
|
321
|
+
|
|
322
|
+
// Index by base token for convenience lookups (first occurrence wins)
|
|
323
|
+
if (!this.spotByBaseToken.has(baseToken.name)) {
|
|
324
|
+
this.spotByBaseToken.set(baseToken.name, instrument);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private addInstrument(instrument: MarketInstrument): void {
|
|
330
|
+
this.instruments.push(instrument);
|
|
331
|
+
this.instrumentsBySymbol.set(instrument.symbol, instrument);
|
|
332
|
+
this.instrumentsByCoin.set(instrument.coin, instrument);
|
|
333
|
+
this.instrumentsByAssetId.set(instrument.assetId, instrument);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Extract dex name from PerpsMeta
|
|
338
|
+
* Root dex (index 0) returns "", HIP3 dexes return the dex name (e.g., "xyz")
|
|
339
|
+
*/
|
|
340
|
+
private extractDexNameFromMeta(
|
|
341
|
+
perpMeta: PerpsMeta,
|
|
342
|
+
dexIndex: number,
|
|
343
|
+
): string {
|
|
344
|
+
if (dexIndex === 0) return ""; // Root dex
|
|
345
|
+
|
|
346
|
+
const firstAsset = perpMeta.universe[0];
|
|
347
|
+
if (!firstAsset) return "";
|
|
348
|
+
|
|
349
|
+
return AssetIdUtils.extractDexName(firstAsset.name) ?? "";
|
|
350
|
+
}
|
|
351
|
+
}
|
|
@@ -73,7 +73,8 @@
|
|
|
73
73
|
]
|
|
74
74
|
],
|
|
75
75
|
"deployerFeeScale": "1.0",
|
|
76
|
-
"lastDeployerFeeScaleChangeTime": "1970-01-01T00:00:00"
|
|
76
|
+
"lastDeployerFeeScaleChangeTime": "1970-01-01T00:00:00",
|
|
77
|
+
"assetToFundingMultiplier": []
|
|
77
78
|
},
|
|
78
79
|
{
|
|
79
80
|
"name": "flx",
|
|
@@ -167,7 +168,21 @@
|
|
|
167
168
|
]
|
|
168
169
|
],
|
|
169
170
|
"deployerFeeScale": "1.0",
|
|
170
|
-
"lastDeployerFeeScaleChangeTime": "1970-01-01T00:00:00"
|
|
171
|
+
"lastDeployerFeeScaleChangeTime": "1970-01-01T00:00:00",
|
|
172
|
+
"assetToFundingMultiplier": [
|
|
173
|
+
[
|
|
174
|
+
"flx:COIN",
|
|
175
|
+
"0.0396816138"
|
|
176
|
+
],
|
|
177
|
+
[
|
|
178
|
+
"flx:CRCL",
|
|
179
|
+
"0.6849327969"
|
|
180
|
+
],
|
|
181
|
+
[
|
|
182
|
+
"flx:TSLA",
|
|
183
|
+
"0.055534957"
|
|
184
|
+
]
|
|
185
|
+
]
|
|
171
186
|
},
|
|
172
187
|
{
|
|
173
188
|
"name": "vntl",
|
|
@@ -217,7 +232,21 @@
|
|
|
217
232
|
]
|
|
218
233
|
],
|
|
219
234
|
"deployerFeeScale": "1.0",
|
|
220
|
-
"lastDeployerFeeScaleChangeTime": "1970-01-01T00:00:00"
|
|
235
|
+
"lastDeployerFeeScaleChangeTime": "1970-01-01T00:00:00",
|
|
236
|
+
"assetToFundingMultiplier": [
|
|
237
|
+
[
|
|
238
|
+
"vntl:ANTHROPIC",
|
|
239
|
+
"0.0053054031"
|
|
240
|
+
],
|
|
241
|
+
[
|
|
242
|
+
"vntl:OPENAI",
|
|
243
|
+
"0.0050794237"
|
|
244
|
+
],
|
|
245
|
+
[
|
|
246
|
+
"vntl:SPACEX",
|
|
247
|
+
"0.010663783"
|
|
248
|
+
]
|
|
249
|
+
]
|
|
221
250
|
},
|
|
222
251
|
{
|
|
223
252
|
"name": "hyna",
|
|
@@ -248,6 +277,7 @@
|
|
|
248
277
|
]
|
|
249
278
|
],
|
|
250
279
|
"deployerFeeScale": "1.0",
|
|
251
|
-
"lastDeployerFeeScaleChangeTime": "2025-12-03T10:32:09.252520621"
|
|
280
|
+
"lastDeployerFeeScaleChangeTime": "2025-12-03T10:32:09.252520621",
|
|
281
|
+
"assetToFundingMultiplier": []
|
|
252
282
|
}
|
|
253
283
|
]
|