Skip to content

Commit 285492d

Browse files
committed
feat: force rest api instead of websocket for write calls
1 parent 4236683 commit 285492d

File tree

2 files changed

+100
-55
lines changed

2 files changed

+100
-55
lines changed

app/components/UI/Perps/services/HyperLiquidClientService.test.ts

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,31 @@ const mockInfoClient = {
2424
const mockSubscriptionClient = {
2525
initialized: true,
2626
};
27-
const mockTransport = {
27+
const mockWsTransport = {
2828
url: 'ws://mock',
2929
close: jest.fn().mockResolvedValue(undefined),
3030
};
31+
const mockHttpTransport = {
32+
url: 'http://mock',
33+
};
3134

3235
jest.mock('@nktkas/hyperliquid', () => ({
3336
ExchangeClient: jest.fn(() => mockExchangeClient),
3437
InfoClient: jest.fn(() => mockInfoClient),
3538
SubscriptionClient: jest.fn(() => mockSubscriptionClient),
36-
WebSocketTransport: jest.fn(() => mockTransport),
39+
WebSocketTransport: jest.fn(() => mockWsTransport),
40+
HttpTransport: jest.fn(() => mockHttpTransport),
3741
}));
3842

3943
// Mock configuration
4044
jest.mock('../constants/hyperLiquidConfig', () => ({
41-
getWebSocketEndpoint: jest.fn((isTestnet: boolean) =>
42-
isTestnet
43-
? 'wss://api.hyperliquid-testnet.xyz/ws'
44-
: 'wss://api.hyperliquid.xyz/ws',
45-
),
4645
HYPERLIQUID_TRANSPORT_CONFIG: {
47-
reconnectAttempts: 5,
48-
reconnectInterval: 1000,
46+
timeout: 10_000,
47+
keepAlive: { interval: 30_000 },
48+
reconnect: {
49+
maxRetries: 5,
50+
connectionTimeout: 10_000,
51+
},
4952
},
5053
}));
5154

@@ -92,7 +95,7 @@ describe('HyperLiquidClientService', () => {
9295
});
9396

9497
describe('Client Initialization', () => {
95-
it('should initialize clients successfully', () => {
98+
it('should initialize clients successfully with dual transports', () => {
9699
service.initialize(mockWallet);
97100

98101
expect(service.isInitialized()).toBe(true);
@@ -102,23 +105,37 @@ describe('HyperLiquidClientService', () => {
102105
InfoClient,
103106
SubscriptionClient,
104107
WebSocketTransport,
108+
HttpTransport,
105109
} = require('@nktkas/hyperliquid');
106110

111+
// Verify HTTP transport uses isTestnet flag (SDK handles endpoint selection)
112+
expect(HttpTransport).toHaveBeenCalledWith({
113+
isTestnet: false,
114+
timeout: 10_000,
115+
});
116+
117+
// Verify WebSocket transport uses isTestnet flag (SDK handles endpoint selection)
107118
expect(WebSocketTransport).toHaveBeenCalledWith({
108-
url: 'wss://api.hyperliquid.xyz/ws',
109-
reconnectAttempts: 5,
110-
reconnectInterval: 1000,
111-
reconnect: {
119+
isTestnet: false,
120+
timeout: 10_000,
121+
keepAlive: { interval: 30_000 },
122+
reconnect: expect.objectContaining({
112123
WebSocket: expect.any(Function),
113-
},
124+
}),
114125
});
126+
127+
// ExchangeClient uses HTTP transport
115128
expect(ExchangeClient).toHaveBeenCalledWith({
116129
wallet: mockWallet,
117-
transport: mockTransport,
130+
transport: mockHttpTransport,
118131
});
119-
expect(InfoClient).toHaveBeenCalledWith({ transport: mockTransport });
132+
133+
// InfoClient uses HTTP transport
134+
expect(InfoClient).toHaveBeenCalledWith({ transport: mockHttpTransport });
135+
136+
// SubscriptionClient uses WebSocket transport
120137
expect(SubscriptionClient).toHaveBeenCalledWith({
121-
transport: mockTransport,
138+
transport: mockWsTransport,
122139
});
123140
});
124141

@@ -140,19 +157,28 @@ describe('HyperLiquidClientService', () => {
140157
const {
141158
ExchangeClient,
142159
WebSocketTransport,
160+
HttpTransport,
143161
} = require('@nktkas/hyperliquid');
144162

163+
// Verify testnet flag is passed (SDK auto-selects testnet endpoints)
164+
expect(HttpTransport).toHaveBeenCalledWith({
165+
isTestnet: true,
166+
timeout: 10_000,
167+
});
168+
145169
expect(WebSocketTransport).toHaveBeenCalledWith({
146-
url: 'wss://api.hyperliquid-testnet.xyz/ws',
147-
reconnectAttempts: 5,
148-
reconnectInterval: 1000,
149-
reconnect: {
170+
isTestnet: true,
171+
timeout: 10_000,
172+
keepAlive: { interval: 30_000 },
173+
reconnect: expect.objectContaining({
150174
WebSocket: expect.any(Function),
151-
},
175+
}),
152176
});
177+
178+
// ExchangeClient uses HTTP transport
153179
expect(ExchangeClient).toHaveBeenCalledWith({
154180
wallet: mockWallet,
155-
transport: mockTransport,
181+
transport: mockHttpTransport,
156182
});
157183
});
158184
});
@@ -269,21 +295,24 @@ describe('HyperLiquidClientService', () => {
269295
service.initialize(mockWallet);
270296
});
271297

272-
it('should disconnect successfully', async () => {
298+
it('should disconnect successfully and close only WebSocket transport', async () => {
273299
await service.disconnect();
274300

275-
expect(mockTransport.close).toHaveBeenCalled();
301+
// Only WebSocket transport should be closed (HTTP is stateless)
302+
expect(mockWsTransport.close).toHaveBeenCalled();
276303
expect(service.getSubscriptionClient()).toBeUndefined();
277304
});
278305

279306
it('should handle disconnect errors gracefully', async () => {
280-
mockTransport.close.mockRejectedValueOnce(new Error('Disconnect failed'));
307+
mockWsTransport.close.mockRejectedValueOnce(
308+
new Error('Disconnect failed'),
309+
);
281310

282311
// Should not throw, error is caught and logged
283312
await expect(service.disconnect()).resolves.not.toThrow();
284313

285314
// Verify the error was attempted to be handled
286-
expect(mockTransport.close).toHaveBeenCalled();
315+
expect(mockWsTransport.close).toHaveBeenCalled();
287316
});
288317

289318
it('should clear all client references after disconnect', async () => {
@@ -348,7 +377,6 @@ describe('HyperLiquidClientService', () => {
348377
'HyperLiquid SDK clients initialized',
349378
expect.objectContaining({
350379
testnet: false,
351-
endpoint: 'wss://api.hyperliquid.xyz/ws',
352380
timestamp: expect.any(String),
353381
connectionState: 'connected',
354382
}),
@@ -367,7 +395,6 @@ describe('HyperLiquidClientService', () => {
367395
'HyperLiquid: Disconnecting SDK clients',
368396
expect.objectContaining({
369397
isTestnet: false,
370-
endpoint: 'wss://api.hyperliquid.xyz/ws',
371398
timestamp: expect.any(String),
372399
}),
373400
);
@@ -381,9 +408,8 @@ describe('HyperLiquidClientService', () => {
381408
service.initialize(mockWallet);
382409

383410
expect(DevLogger.log).toHaveBeenCalledWith(
384-
'HyperLiquid: Creating WebSocket transport',
411+
'HyperLiquid: Creating transports',
385412
expect.objectContaining({
386-
endpoint: 'wss://api.hyperliquid.xyz/ws',
387413
isTestnet: false,
388414
timestamp: expect.any(String),
389415
}),

app/components/UI/Perps/services/HyperLiquidClientService.ts

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import {
22
ExchangeClient,
3+
HttpTransport,
34
InfoClient,
45
SubscriptionClient,
56
WebSocketTransport,
67
} from '@nktkas/hyperliquid';
78
import { DevLogger } from '../../../../core/SDKConnect/utils/DevLogger';
8-
import {
9-
getWebSocketEndpoint,
10-
HYPERLIQUID_TRANSPORT_CONFIG,
11-
} from '../constants/hyperLiquidConfig';
9+
import { HYPERLIQUID_TRANSPORT_CONFIG } from '../constants/hyperLiquidConfig';
1210
import type { HyperLiquidNetwork } from '../types/config';
1311
import { strings } from '../../../../../locales/i18n';
1412
import type { CandleData } from '../types/perps-types';
@@ -42,7 +40,8 @@ export class HyperLiquidClientService {
4240
private exchangeClient?: ExchangeClient;
4341
private infoClient?: InfoClient;
4442
private subscriptionClient?: SubscriptionClient<WebSocketTransport>;
45-
private transport?: WebSocketTransport;
43+
private wsTransport?: WebSocketTransport;
44+
private httpTransport?: HttpTransport;
4645
private isTestnet: boolean;
4746
private connectionState: WebSocketConnectionState =
4847
WebSocketConnectionState.DISCONNECTED;
@@ -73,26 +72,35 @@ export class HyperLiquidClientService {
7372
}): void {
7473
try {
7574
this.connectionState = WebSocketConnectionState.CONNECTING;
76-
this.transport = this.createTransport();
75+
this.createTransports();
76+
77+
// Ensure transports are created
78+
if (!this.httpTransport || !this.wsTransport) {
79+
throw new Error('Failed to create transports');
80+
}
7781

7882
// Wallet adapter implements AbstractViemJsonRpcAccount interface with signTypedData method
83+
// ExchangeClient uses HTTP transport for write operations (orders, approvals, etc.)
7984
this.exchangeClient = new ExchangeClient({
8085
wallet: wallet as any, // eslint-disable-line @typescript-eslint/no-explicit-any -- Type widening for SDK compatibility
81-
transport: this.transport,
86+
transport: this.httpTransport,
8287
});
8388

84-
this.infoClient = new InfoClient({ transport: this.transport });
89+
// InfoClient uses HTTP transport for read operations (queries, metadata, etc.)
90+
this.infoClient = new InfoClient({ transport: this.httpTransport });
91+
92+
// SubscriptionClient uses WebSocket transport for real-time pub/sub (price feeds, position updates)
8593
this.subscriptionClient = new SubscriptionClient({
86-
transport: this.transport,
94+
transport: this.wsTransport,
8795
});
8896

8997
this.connectionState = WebSocketConnectionState.CONNECTED;
9098

9199
DevLogger.log('HyperLiquid SDK clients initialized', {
92100
testnet: this.isTestnet,
93-
endpoint: getWebSocketEndpoint(this.isTestnet),
94101
timestamp: new Date().toISOString(),
95102
connectionState: this.connectionState,
103+
note: 'Using HTTP for InfoClient/ExchangeClient, WebSocket for SubscriptionClient',
96104
});
97105
} catch (error) {
98106
this.connectionState = WebSocketConnectionState.DISCONNECTED;
@@ -102,19 +110,30 @@ export class HyperLiquidClientService {
102110
}
103111

104112
/**
105-
* Create WebSocket transport with configuration
113+
* Create HTTP and WebSocket transports
114+
* - HTTP for InfoClient and ExchangeClient (request/response operations)
115+
* - WebSocket for SubscriptionClient (real-time pub/sub)
116+
*
117+
* Both transports use SDK's built-in endpoint resolution via isTestnet flag
106118
*/
107-
private createTransport(): WebSocketTransport {
108-
const wsUrl = getWebSocketEndpoint(this.isTestnet);
109-
110-
DevLogger.log('HyperLiquid: Creating WebSocket transport', {
111-
endpoint: wsUrl,
119+
private createTransports(): void {
120+
DevLogger.log('HyperLiquid: Creating transports', {
112121
isTestnet: this.isTestnet,
113122
timestamp: new Date().toISOString(),
123+
note: 'SDK will auto-select endpoints based on isTestnet flag',
114124
});
115125

116-
return new WebSocketTransport({
117-
url: wsUrl,
126+
// HTTP transport for request/response operations (InfoClient, ExchangeClient)
127+
// SDK automatically selects: mainnet (https://api.hyperliquid.xyz) or testnet (https://api.hyperliquid-testnet.xyz)
128+
this.httpTransport = new HttpTransport({
129+
isTestnet: this.isTestnet,
130+
timeout: HYPERLIQUID_TRANSPORT_CONFIG.timeout,
131+
});
132+
133+
// WebSocket transport for real-time subscriptions (SubscriptionClient)
134+
// SDK automatically selects: mainnet (wss://api.hyperliquid.xyz/ws) or testnet (wss://api.hyperliquid-testnet.xyz/ws)
135+
this.wsTransport = new WebSocketTransport({
136+
isTestnet: this.isTestnet,
118137
...HYPERLIQUID_TRANSPORT_CONFIG,
119138
reconnect: {
120139
WebSocket, // Use React Native's global WebSocket
@@ -361,15 +380,14 @@ export class HyperLiquidClientService {
361380

362381
DevLogger.log('HyperLiquid: Disconnecting SDK clients', {
363382
isTestnet: this.isTestnet,
364-
endpoint: getWebSocketEndpoint(this.isTestnet),
365383
timestamp: new Date().toISOString(),
366384
connectionState: this.connectionState,
367385
});
368386

369-
// Close the WebSocket connection via transport
370-
if (this.transport) {
387+
// Close WebSocket transport only (HTTP is stateless)
388+
if (this.wsTransport) {
371389
try {
372-
await this.transport.close();
390+
await this.wsTransport.close();
373391
DevLogger.log('HyperLiquid: Closed WebSocket transport', {
374392
timestamp: new Date().toISOString(),
375393
});
@@ -384,7 +402,8 @@ export class HyperLiquidClientService {
384402
this.subscriptionClient = undefined;
385403
this.exchangeClient = undefined;
386404
this.infoClient = undefined;
387-
this.transport = undefined;
405+
this.wsTransport = undefined;
406+
this.httpTransport = undefined;
388407

389408
this.connectionState = WebSocketConnectionState.DISCONNECTED;
390409

0 commit comments

Comments
 (0)