hyperliquid-sdk
Version:
<<< Typescript SDK for the Hyperliquid API >>>
214 lines (189 loc) • 6 kB
text/typescript
import { HttpApi } from './helpers';
import * as CONSTANTS from '../types/constants';
export class SymbolConversion {
private assetToIndexMap: Map<string, number> = new Map();
private exchangeToInternalNameMap: Map<string, string> = new Map();
private httpApi: HttpApi;
private refreshIntervalMs: number = 60000;
private refreshInterval: NodeJS.Timeout | null = null;
private initializationPromise: Promise<void>;
constructor(baseURL: string, rateLimiter: any) {
this.httpApi = new HttpApi(baseURL, CONSTANTS.ENDPOINTS.INFO, rateLimiter);
this.initializationPromise = this.initialize();
}
private async initialize(): Promise<void> {
await this.refreshAssetMaps();
this.startPeriodicRefresh();
}
private async refreshAssetMaps(): Promise<void> {
try {
const [perpMeta, spotMeta] = await Promise.all([
// TODO: Fix any
this.httpApi.makeRequest<any>({
type: CONSTANTS.InfoType.PERPS_META_AND_ASSET_CTXS,
}),
// TODO: Fix any
this.httpApi.makeRequest<any>({
type: CONSTANTS.InfoType.SPOT_META_AND_ASSET_CTXS,
}),
]);
this.assetToIndexMap.clear();
this.exchangeToInternalNameMap.clear();
// Handle perpetual assets
perpMeta[0].universe.forEach((asset: { name: string }, index: number) => {
const internalName = `${asset.name}-PERP`;
this.assetToIndexMap.set(internalName, index);
this.exchangeToInternalNameMap.set(asset.name, internalName);
});
// Handle spot assets
spotMeta[0].tokens.forEach((token: any) => {
const universeItem = spotMeta[0].universe.find(
(item: any) => item.tokens[0] === token.index,
);
if (universeItem) {
const internalName = `${token.name}-SPOT`;
const exchangeName = universeItem.name;
const index = universeItem.index;
this.assetToIndexMap.set(internalName, 10000 + index);
this.exchangeToInternalNameMap.set(exchangeName, internalName);
}
});
} catch (error) {
console.error('Failed to refresh asset maps:', error);
}
}
private startPeriodicRefresh(): void {
// TODO: Remove interval
this.refreshInterval = setInterval(() => {
this.refreshAssetMaps();
}, this.refreshIntervalMs);
}
public stopPeriodicRefresh(): void {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
this.refreshInterval = null;
}
}
private async ensureInitialized(): Promise<void> {
await this.initializationPromise;
}
public async getInternalName(
exchangeName: string,
): Promise<string | undefined> {
await this.ensureInitialized();
return this.exchangeToInternalNameMap.get(exchangeName);
}
public async getExchangeName(
internalName: string,
): Promise<string | undefined> {
await this.ensureInitialized();
for (const [
exchangeName,
name,
] of this.exchangeToInternalNameMap.entries()) {
if (name === internalName) {
return exchangeName;
}
}
return undefined;
}
public async getAssetIndex(assetSymbol: string): Promise<number | undefined> {
await this.ensureInitialized();
return this.assetToIndexMap.get(assetSymbol);
}
public async getAllAssets(): Promise<{ perp: string[]; spot: string[] }> {
await this.ensureInitialized();
const perp: string[] = [];
const spot: string[] = [];
for (const [asset, index] of this.assetToIndexMap.entries()) {
if (asset.endsWith('-PERP')) {
perp.push(asset);
} else if (asset.endsWith('-SPOT')) {
spot.push(asset);
}
}
return { perp, spot };
}
async convertSymbol(
symbol: string,
mode: string = '',
symbolMode: string = '',
): Promise<string> {
await this.ensureInitialized();
let rSymbol: string;
if (mode === 'reverse') {
for (const [key, value] of this.exchangeToInternalNameMap.entries()) {
if (value === symbol) {
return key;
}
}
rSymbol = symbol;
} else {
rSymbol = this.exchangeToInternalNameMap.get(symbol) || symbol;
}
if (symbolMode === 'SPOT') {
if (!rSymbol.endsWith('-SPOT')) {
rSymbol = symbol + '-SPOT';
}
} else if (symbolMode === 'PERP') {
if (!rSymbol.endsWith('-PERP')) {
rSymbol = symbol + '-PERP';
}
}
return rSymbol;
}
async convertSymbolsInObject(
obj: any,
symbolsFields: Array<string> = ['coin', 'symbol'],
symbolMode: string = '',
): Promise<any> {
await this.ensureInitialized();
if (typeof obj !== 'object' || obj === null) {
return this.convertToNumber(obj);
}
if (Array.isArray(obj)) {
return Promise.all(
obj.map((item) =>
this.convertSymbolsInObject(item, symbolsFields, symbolMode),
),
);
}
const convertedObj: any = {};
for (const [key, value] of Object.entries(obj)) {
if (symbolsFields.includes(key)) {
convertedObj[key] = await this.convertSymbol(
value as string,
'',
symbolMode,
);
} else if (key === 'side') {
convertedObj[key] =
value === 'A' ? 'sell' : value === 'B' ? 'buy' : value;
} else {
convertedObj[key] = await this.convertSymbolsInObject(
value,
symbolsFields,
symbolMode,
);
}
}
return convertedObj;
}
convertToNumber(value: any): any {
if (typeof value === 'string') {
if (/^-?\d+$/.test(value)) {
return parseInt(value, 10);
} else if (/^-?\d*\.\d+$/.test(value)) {
return parseFloat(value);
}
}
return value;
}
async convertResponse(
response: any,
symbolsFields: string[] = ['coin', 'symbol'],
symbolMode: string = '',
): Promise<any> {
return this.convertSymbolsInObject(response, symbolsFields, symbolMode);
}
}