@rabby-wallet/hyperliquid-sdk 1.1.0-beta.1 → 1.1.0-beta.3

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.
@@ -1,4 +1,4 @@
1
- import type { OrderResponse, CancelResponse, ExchangeClientConfig, PlaceOrderParams, MultiOrderParams, CancelOrderParams, ModifyOrderParams, WithdrawParams, ApproveBuilderFeeParams, PrepareApproveBuilderFeeResult, SendApproveParams, MarketOrderParams, UpdateLeverageParams, BindTpslByOrderIdParams } from '../types';
1
+ import type { OrderResponse, CancelResponse, ExchangeClientConfig, PlaceOrderParams, MultiOrderParams, CancelOrderParams, ModifyOrderParams, WithdrawParams, ApproveBuilderFeeParams, PrepareApproveBuilderFeeResult, SendApproveParams, MarketOrderParams, UpdateLeverageParams, BindTpslByOrderIdParams, UpdateIsolatedMarginParams } from '../types';
2
2
  /**
3
3
  * Client for executing trades on Hyperliquid (perpetuals only)
4
4
  * Only includes essential trading APIs as specified
@@ -71,6 +71,7 @@ export declare class ExchangeClient {
71
71
  * Modify an existing order
72
72
  */
73
73
  modifyOrder(params: ModifyOrderParams): Promise<any>;
74
+ updateIsolatedMargin(params: UpdateIsolatedMarginParams): Promise<any>;
74
75
  /**
75
76
  * Set referrer for 10% fee
76
77
  */
@@ -451,6 +451,24 @@ class ExchangeClient {
451
451
  });
452
452
  });
453
453
  }
454
+ updateIsolatedMargin(params) {
455
+ return __awaiter(this, void 0, void 0, function* () {
456
+ const nonce = Date.now();
457
+ const assetIndex = yield this.symbolConversion.getAssetIndex(params.coin);
458
+ const action = {
459
+ type: constants_1.ExchangeType.UPDATE_ISOLATED_MARGIN,
460
+ asset: assetIndex,
461
+ isBuy: true,
462
+ ntli: (0, number_1.floatToInt)(params.value, 6), // 6 decimals
463
+ };
464
+ const signature = (0, signer_1.signL1AgentAction)(this.getAgentPrivateKey(), action, this.isTestnet, nonce);
465
+ return this.httpClient.exchange({
466
+ action,
467
+ nonce,
468
+ signature,
469
+ });
470
+ });
471
+ }
454
472
  /**
455
473
  * Set referrer for 10% fee
456
474
  */
@@ -1,4 +1,4 @@
1
- import type { AllMids, L2Book, Candle, WsOrder, WebData2, WsActiveAssetCtx, WsUserFills } from '../types';
1
+ import type { AllMids, L2Book, Candle, WsOrder, WebData2, WsActiveAssetCtx, WsUserFills, WsClearinghouseState } from '../types';
2
2
  export interface WebSocketClientConfig {
3
3
  masterAddress?: string;
4
4
  isTestnet?: boolean;
@@ -43,6 +43,7 @@ export declare class WebSocketClient {
43
43
  * Disconnect from the WebSocket
44
44
  */
45
45
  disconnect(): void;
46
+ private getSubscriptionKey;
46
47
  /**
47
48
  * Subscribe to a channel
48
49
  */
@@ -84,6 +85,7 @@ export declare class WebSocketClient {
84
85
  * Includes positions, margin summary, open orders, and other user data
85
86
  */
86
87
  subscribeToWebData2(callback: SubscriptionCallback<WebData2>): Subscription;
88
+ subscribeToClearinghouseState(user: string | string[], callback: SubscriptionCallback<WsClearinghouseState>): Subscription;
87
89
  /**
88
90
  * Check if WebSocket is connected
89
91
  */
@@ -100,4 +102,10 @@ export declare class WebSocketClient {
100
102
  * Reset reconnection attempts and enable auto-reconnect
101
103
  */
102
104
  resetReconnectState(): void;
105
+ /**
106
+ * Handle application state change (for React Native)
107
+ * Call this method when the app state changes (e.g., active -> background, background -> active)
108
+ * @param state 'active' | 'background' | 'inactive'
109
+ */
110
+ handleAppStateChange(state: string): void;
103
111
  }
@@ -77,8 +77,9 @@ class WebSocketClient {
77
77
  this.pendingSubscriptions.forEach(({ message, callback }) => {
78
78
  var _a;
79
79
  if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
80
+ const subKey = this.getSubscriptionKey(message.subscription);
80
81
  this.subscriptions.set(message.subscription.type, callback);
81
- this.activeSubscriptions.set(message.subscription.type, { message, callback });
82
+ this.activeSubscriptions.set(subKey, { message, callback });
82
83
  this.ws.send(JSON.stringify(Object.assign(Object.assign({}, message), { method: 'subscribe' })));
83
84
  }
84
85
  });
@@ -87,6 +88,7 @@ class WebSocketClient {
87
88
  resolve();
88
89
  };
89
90
  this.ws.onmessage = (event) => {
91
+ var _a;
90
92
  try {
91
93
  const data = JSON.parse(event.data);
92
94
  // Handle pong response
@@ -95,9 +97,40 @@ class WebSocketClient {
95
97
  return;
96
98
  }
97
99
  const type = data.channel;
98
- const callback = this.subscriptions.get(type);
99
- if (callback) {
100
- callback(data.data);
100
+ // Iterate through active subscriptions to find matching ones
101
+ let hasDispatched = false;
102
+ for (const { message, callback } of this.activeSubscriptions.values()) {
103
+ const subParams = message.subscription;
104
+ if (subParams.type === type) {
105
+ let isMatch = true;
106
+ // Filter based on subscription parameters
107
+ if (type === 'l2Book' && data.data.coin !== subParams.coin)
108
+ isMatch = false;
109
+ if (type === 'trades' && ((_a = data.data[0]) === null || _a === void 0 ? void 0 : _a.coin) !== subParams.coin)
110
+ isMatch = false;
111
+ if (type === 'candle' && (data.data.s !== subParams.coin || data.data.i !== subParams.interval))
112
+ isMatch = false;
113
+ if (type === 'activeAssetCtx' && data.data.coin !== subParams.coin)
114
+ isMatch = false;
115
+ // For user-specific data
116
+ if (subParams.user) {
117
+ // If response data contains user, verify it matches
118
+ if (data.data && data.data.user && data.data.user.toLowerCase() !== subParams.user.toLowerCase()) {
119
+ isMatch = false;
120
+ }
121
+ }
122
+ if (isMatch) {
123
+ callback(data.data);
124
+ hasDispatched = true;
125
+ }
126
+ }
127
+ }
128
+ // Fallback to simple type map if no active subscription matched (backward compatibility or if activeSubscriptions missing)
129
+ if (!hasDispatched) {
130
+ const callback = this.subscriptions.get(type);
131
+ if (callback) {
132
+ callback(data.data);
133
+ }
101
134
  }
102
135
  }
103
136
  catch (error) {
@@ -113,8 +146,9 @@ class WebSocketClient {
113
146
  if (!this.isManualDisconnect && this.config.autoReconnect) {
114
147
  // Save current active subscriptions to pending for reconnection
115
148
  if (this.activeSubscriptions.size > 0) {
116
- this.activeSubscriptions.forEach((subscription, type) => {
117
- const existingPending = this.pendingSubscriptions.find(sub => sub.message.subscription.type === type);
149
+ this.activeSubscriptions.forEach((subscription) => {
150
+ const subKey = this.getSubscriptionKey(subscription.message.subscription);
151
+ const existingPending = this.pendingSubscriptions.find(sub => this.getSubscriptionKey(sub.message.subscription) === subKey);
118
152
  if (!existingPending) {
119
153
  this.pendingSubscriptions.push(subscription);
120
154
  }
@@ -178,15 +212,20 @@ class WebSocketClient {
178
212
  this.pendingSubscriptions = [];
179
213
  this.reconnectAttempts = 0; // 重置重连计数
180
214
  }
215
+ getSubscriptionKey(subscription) {
216
+ return JSON.stringify(subscription);
217
+ }
181
218
  /**
182
219
  * Subscribe to a channel
183
220
  */
184
221
  subscribe(message, callback) {
185
222
  var _a;
186
223
  const subscriptionData = { message, callback };
224
+ const subKey = this.getSubscriptionKey(message.subscription);
225
+ const type = message.subscription.type;
187
226
  if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
188
- this.subscriptions.set(message.subscription.type, callback);
189
- this.activeSubscriptions.set(message.subscription.type, subscriptionData);
227
+ this.subscriptions.set(type, callback);
228
+ this.activeSubscriptions.set(subKey, subscriptionData);
190
229
  this.ws.send(JSON.stringify(Object.assign(Object.assign({}, message), { method: 'subscribe' })));
191
230
  }
192
231
  else {
@@ -200,14 +239,23 @@ class WebSocketClient {
200
239
  return {
201
240
  unsubscribe: () => {
202
241
  var _a;
203
- const type = message.subscription.type;
204
- this.subscriptions.delete(type);
205
- this.activeSubscriptions.delete(type);
242
+ this.activeSubscriptions.delete(subKey);
243
+ // Only remove the global callback if no other active subscriptions use this channel type
244
+ let hasOtherSubs = false;
245
+ for (const sub of this.activeSubscriptions.values()) {
246
+ if (sub.message.subscription.type === type) {
247
+ hasOtherSubs = true;
248
+ break;
249
+ }
250
+ }
251
+ if (!hasOtherSubs) {
252
+ this.subscriptions.delete(type);
253
+ }
206
254
  if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
207
255
  this.ws.send(JSON.stringify(Object.assign(Object.assign({}, message), { method: 'unsubscribe' })));
208
256
  }
209
257
  // Remove from pending subscriptions
210
- this.pendingSubscriptions = this.pendingSubscriptions.filter(sub => sub.message.subscription.type !== type);
258
+ this.pendingSubscriptions = this.pendingSubscriptions.filter(sub => this.getSubscriptionKey(sub.message.subscription) !== subKey);
211
259
  },
212
260
  };
213
261
  }
@@ -296,6 +344,20 @@ class WebSocketClient {
296
344
  subscription: { type: 'webData2', user: this.config.masterAddress },
297
345
  }, callback);
298
346
  }
347
+ subscribeToClearinghouseState(user, callback) {
348
+ const users = Array.isArray(user) ? user : [user];
349
+ if (users.length === 0) {
350
+ throw new Error('user address is empty');
351
+ }
352
+ const subscriptions = users.map(u => this.subscribe({
353
+ subscription: { type: 'clearinghouseState', user: u || this.config.masterAddress },
354
+ }, callback));
355
+ return {
356
+ unsubscribe: () => {
357
+ subscriptions.forEach(sub => sub.unsubscribe());
358
+ }
359
+ };
360
+ }
299
361
  /**
300
362
  * Check if WebSocket is connected
301
363
  */
@@ -323,5 +385,32 @@ class WebSocketClient {
323
385
  this.reconnectAttempts = 0;
324
386
  this.isManualDisconnect = false;
325
387
  }
388
+ /**
389
+ * Handle application state change (for React Native)
390
+ * Call this method when the app state changes (e.g., active -> background, background -> active)
391
+ * @param state 'active' | 'background' | 'inactive'
392
+ */
393
+ handleAppStateChange(state) {
394
+ if (state === 'active') {
395
+ // App returned to foreground
396
+ if (!this.isConnected && !this.isManualDisconnect && this.config.autoReconnect) {
397
+ console.log('App returned to foreground, reconnecting WebSocket...');
398
+ this.reconnectAttempts = 0;
399
+ this.connect().catch(console.error);
400
+ }
401
+ }
402
+ else if (state === 'background') {
403
+ // App went to background
404
+ // Stop heartbeat and close connection to save resources
405
+ this.stopHeartbeat();
406
+ if (this.ws) {
407
+ // We don't set isManualDisconnect to true here because we want auto-reconnect
408
+ // to kick in when we come back to 'active' state
409
+ this.ws.close();
410
+ this.ws = null;
411
+ console.log('App backgrounded: WebSocket disconnected to save resources');
412
+ }
413
+ }
414
+ }
326
415
  }
327
416
  exports.WebSocketClient = WebSocketClient;
@@ -96,6 +96,11 @@ export type Meta = {
96
96
  universe: AssetInfo[];
97
97
  marginTables: [number, MarginTable][];
98
98
  };
99
+ export interface WsClearinghouseState {
100
+ dex: any;
101
+ user: string;
102
+ clearinghouseState: ClearinghouseState;
103
+ }
99
104
  export interface ClearinghouseState {
100
105
  assetPositions: AssetPosition[];
101
106
  crossMaintenanceMarginUsed: string;
@@ -393,6 +398,10 @@ export interface CancelOrderParams {
393
398
  coin: string;
394
399
  oid: OrderId;
395
400
  }
401
+ export interface UpdateIsolatedMarginParams {
402
+ coin: string;
403
+ value: number;
404
+ }
396
405
  export interface ModifyOrderParams {
397
406
  oid: OrderId;
398
407
  coin: string;
@@ -10,3 +10,4 @@
10
10
  * @returns The normalized string without trailing zeros
11
11
  */
12
12
  export declare function removeTrailingZeros(value: string): string;
13
+ export declare function floatToInt(x: number, power: number): number;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.removeTrailingZeros = removeTrailingZeros;
4
+ exports.floatToInt = floatToInt;
4
5
  /**
5
6
  * Removes trailing zeros from a string representation of a number.
6
7
  * This is useful when working with price and size fields directly.
@@ -20,3 +21,10 @@ function removeTrailingZeros(value) {
20
21
  return '0';
21
22
  return normalized;
22
23
  }
24
+ function floatToInt(x, power) {
25
+ const withDecimals = x * Math.pow(10, power);
26
+ if (Math.abs(Math.round(withDecimals) - withDecimals) >= 1e-3) {
27
+ throw new Error(`floatToInt causes rounding: ${x}`);
28
+ }
29
+ return Math.round(withDecimals);
30
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rabby-wallet/hyperliquid-sdk",
3
- "version": "1.1.0-beta.1",
3
+ "version": "1.1.0-beta.3",
4
4
  "description": "Simplified Hyperliquid Perpetuals Trading SDK for Frontend Applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",