hyperliquid-rb 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b7592a75a389665ac7ccb861b5cf9f4f500d16b712343f41b0acf5411c8408dc
4
+ data.tar.gz: 5c7d881e948ae92752a8b0fa58b6738cec1318ff3ebd5e27764cac4919475460
5
+ SHA512:
6
+ metadata.gz: dea8bc10e5b32c4e9695a11004f6c85d7bd8ef84c0afa01d3996c11d0813dbf5700a71a2203b9dc3d4bb0c2bebf262ab537b119ea122385a9ffb35f643db402e
7
+ data.tar.gz: 90965b87faee7ee1e1d0ffdaca66c1e34bdd11a957aeefee5c10ab0176a10f16759055b5141307aad8a02cf7de09703464309edda514a87d2dcebc722e50642e
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Deltabadger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,460 @@
1
+ # Hyperliquid Ruby SDK
2
+
3
+ Complete Ruby SDK for the [Hyperliquid](https://hyperliquid.xyz) DEX. Covers 100% of the official Python SDK — trading, market data, EIP-712 signing, WebSocket subscriptions, and account management.
4
+
5
+ Verified with 314 tests including 141 cross-library test vectors generated from the official Python SDK to ensure byte-identical signing output.
6
+
7
+ ## Installation
8
+
9
+ ```ruby
10
+ gem "hyperliquid-rb"
11
+ ```
12
+
13
+ ### System Dependencies
14
+
15
+ The `eth` gem requires `libsecp256k1`:
16
+
17
+ ```bash
18
+ # macOS
19
+ brew install secp256k1
20
+
21
+ # Ubuntu/Debian
22
+ apt-get install libsecp256k1-dev
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ### Read-Only (no wallet needed)
28
+
29
+ ```ruby
30
+ require "hyperliquid"
31
+
32
+ info = Hyperliquid::Info.new(skip_ws: true)
33
+
34
+ info.all_mids # => {"ETH" => "1800.5", "BTC" => "45000.0", ...}
35
+ info.meta # Perpetual market metadata
36
+ info.l2_snapshot("ETH") # Order book
37
+ info.user_state("0x...") # Positions and margin
38
+ info.open_orders("0x...") # Open orders
39
+ ```
40
+
41
+ ### Trading
42
+
43
+ ```ruby
44
+ exchange = Hyperliquid::Exchange.new(private_key: "0x...")
45
+
46
+ # Limit order
47
+ exchange.order("ETH",
48
+ is_buy: true,
49
+ sz: 1.0,
50
+ limit_px: 1800.0,
51
+ order_type: { limit: { "tif" => "Gtc" } }
52
+ )
53
+
54
+ # Market buy with 5% slippage
55
+ exchange.market_open("ETH", is_buy: true, sz: 1.0)
56
+
57
+ # Close entire position at market
58
+ exchange.market_close("ETH")
59
+
60
+ # Cancel by order ID
61
+ exchange.cancel("ETH", 123)
62
+
63
+ # Cancel by client order ID
64
+ exchange.cancel_by_cloid("ETH", cloid)
65
+ ```
66
+
67
+ ### Testnet
68
+
69
+ ```ruby
70
+ info = Hyperliquid::Info.new(base_url: Hyperliquid::TESTNET_URL, skip_ws: true)
71
+ exchange = Hyperliquid::Exchange.new(
72
+ private_key: "0x...",
73
+ base_url: Hyperliquid::TESTNET_URL
74
+ )
75
+ ```
76
+
77
+ ### WebSocket Subscriptions
78
+
79
+ ```ruby
80
+ info = Hyperliquid::Info.new
81
+
82
+ # Subscribe to all mid prices
83
+ sub_id = info.subscribe(
84
+ { "type" => "allMids" },
85
+ ->(msg) { puts msg["data"] }
86
+ )
87
+
88
+ # Subscribe to order book
89
+ info.subscribe(
90
+ { "type" => "l2Book", "coin" => "ETH" },
91
+ ->(msg) { puts msg["data"]["levels"] }
92
+ )
93
+
94
+ # Subscribe to user events
95
+ info.subscribe(
96
+ { "type" => "userEvents", "user" => "0x..." },
97
+ ->(msg) { puts msg }
98
+ )
99
+
100
+ # Unsubscribe
101
+ info.unsubscribe({ "type" => "allMids" }, sub_id)
102
+
103
+ # Disconnect
104
+ info.disconnect_websocket
105
+ ```
106
+
107
+ WebSocket subscription types: `allMids`, `l2Book`, `trades`, `bbo`, `candle`, `userEvents`, `userFills`, `orderUpdates`, `userFundings`, `userNonFundingLedgerUpdates`, `webData2`, `activeAssetCtx`, `activeAssetData`.
108
+
109
+ ### Vault Trading
110
+
111
+ ```ruby
112
+ exchange = Hyperliquid::Exchange.new(
113
+ private_key: "0x...",
114
+ vault_address: "0xvault..."
115
+ )
116
+ # All subsequent orders go through the vault
117
+ ```
118
+
119
+ ### Agent Trading
120
+
121
+ ```ruby
122
+ # Approve an agent (generates a new key)
123
+ result, agent_key = exchange.approve_agent(name: "my-bot")
124
+
125
+ # Trade as agent
126
+ agent_exchange = Hyperliquid::Exchange.new(
127
+ private_key: agent_key,
128
+ account_address: "0xowner..."
129
+ )
130
+ ```
131
+
132
+ ## API Reference
133
+
134
+ ### `Hyperliquid::Info`
135
+
136
+ Read-only API — no private key required.
137
+
138
+ ```ruby
139
+ info = Hyperliquid::Info.new(
140
+ base_url: Hyperliquid::MAINNET_URL, # or TESTNET_URL
141
+ skip_ws: false # set true to disable WebSocket
142
+ )
143
+ ```
144
+
145
+ #### Market Data
146
+
147
+ | Method | Description |
148
+ |--------|-------------|
149
+ | `meta(dex:)` | Perpetual metadata (universe, asset info) |
150
+ | `spot_meta` | Spot market metadata |
151
+ | `meta_and_asset_ctxs` | Perp metadata with live contexts (funding, OI) |
152
+ | `spot_meta_and_asset_ctxs` | Spot metadata with live contexts |
153
+ | `all_mids(dex:)` | All mid prices |
154
+ | `l2_snapshot(coin, n_levels:)` | L2 order book |
155
+ | `candles_snapshot(coin, interval:, start_time:, end_time:)` | Candle data |
156
+ | `perp_dexs` | List of perp dexes |
157
+
158
+ #### User State
159
+
160
+ | Method | Description |
161
+ |--------|-------------|
162
+ | `user_state(address, dex:)` | Perpetual account state |
163
+ | `spot_user_state(address)` | Spot account state |
164
+ | `open_orders(address, dex:)` | Open orders |
165
+ | `frontend_open_orders(address, dex:)` | Open orders with frontend info |
166
+ | `user_fills(address)` | Trade history |
167
+ | `user_fills_by_time(address, start_time:, end_time:, aggregate_by_time:)` | Fills by time range |
168
+ | `user_fees(address)` | Fee rates and volume |
169
+ | `user_funding(address, start_time:, end_time:)` | User funding history |
170
+ | `user_non_funding_ledger_updates(address, start_time:, end_time:)` | Non-funding ledger updates |
171
+ | `user_rate_limit(address)` | API rate limit status |
172
+ | `user_role(address)` | User role and account type |
173
+ | `extra_agents(address)` | Approved agents |
174
+ | `portfolio(address)` | Portfolio performance |
175
+ | `user_twap_slice_fills(address)` | TWAP slice fills |
176
+ | `user_vault_equities(address)` | Vault equity positions |
177
+ | `sub_accounts(address)` | Sub-accounts |
178
+
179
+ #### Orders
180
+
181
+ | Method | Description |
182
+ |--------|-------------|
183
+ | `order_status(address, oid)` | Order status by OID or CLOID |
184
+ | `historical_orders(address)` | Historical orders (up to 2000) |
185
+
186
+ #### Funding
187
+
188
+ | Method | Description |
189
+ |--------|-------------|
190
+ | `funding_history(coin, start_time:, end_time:)` | Funding rate history |
191
+ | `predicted_fundings` | Predicted funding rates |
192
+
193
+ #### Staking
194
+
195
+ | Method | Description |
196
+ |--------|-------------|
197
+ | `user_staking_summary(address)` | Staking summary |
198
+ | `user_staking_delegations(address)` | Delegations per validator |
199
+ | `user_staking_rewards(address)` | Historic rewards |
200
+ | `delegator_history(address)` | Full delegator history |
201
+
202
+ #### Other
203
+
204
+ | Method | Description |
205
+ |--------|-------------|
206
+ | `referral(address)` | Referral state |
207
+ | `query_user_to_multi_sig_signers(address)` | Multi-sig signers |
208
+ | `query_perp_deploy_auction_status` | Perp deploy auction status |
209
+ | `query_spot_deploy_auction_status(address)` | Spot deploy state |
210
+ | `query_user_dex_abstraction_state(address)` | Dex abstraction state |
211
+ | `query_user_abstraction_state(address)` | User abstraction state |
212
+
213
+ #### Asset Mapping
214
+
215
+ | Method | Description |
216
+ |--------|-------------|
217
+ | `name_to_asset(name)` | Map coin name to asset index |
218
+ | `coin_to_asset(coin)` | Map perp coin to asset index |
219
+ | `spot_coin_to_asset(coin)` | Map spot coin to asset index |
220
+ | `asset_to_sz_decimals(asset)` | Size decimals for an asset |
221
+ | `name_to_coin(name)` | Display name to wire coin name |
222
+ | `refresh_coin_mappings!` | Clear cached mappings |
223
+
224
+ #### WebSocket
225
+
226
+ | Method | Description |
227
+ |--------|-------------|
228
+ | `subscribe(subscription, callback)` | Subscribe to a channel, returns subscription_id |
229
+ | `unsubscribe(subscription, subscription_id)` | Unsubscribe from a channel |
230
+ | `disconnect_websocket` | Close WebSocket and stop ping thread |
231
+
232
+ ### `Hyperliquid::Exchange`
233
+
234
+ Write API — requires private key.
235
+
236
+ ```ruby
237
+ exchange = Hyperliquid::Exchange.new(
238
+ private_key: "0x...",
239
+ base_url: Hyperliquid::MAINNET_URL,
240
+ vault_address: nil, # default vault for all operations
241
+ account_address: nil, # for agent trading
242
+ skip_ws: false
243
+ )
244
+ ```
245
+
246
+ #### Orders
247
+
248
+ | Method | Description |
249
+ |--------|-------------|
250
+ | `order(coin, is_buy:, sz:, limit_px:, order_type:, ...)` | Place single order |
251
+ | `bulk_orders(orders, grouping:, builder:)` | Place multiple orders |
252
+ | `market_open(coin, is_buy:, sz:, px:, slippage:, ...)` | Market buy/sell (IOC + slippage) |
253
+ | `market_close(coin, sz:, px:, slippage:, ...)` | Close position at market |
254
+ | `modify_order(oid, coin:, is_buy:, sz:, limit_px:, order_type:, ...)` | Modify an order |
255
+ | `bulk_modify_orders(modifications)` | Modify multiple orders |
256
+ | `cancel(coin, oid)` | Cancel by order ID |
257
+ | `cancel_by_cloid(coin, cloid)` | Cancel by client order ID |
258
+ | `bulk_cancel(cancels)` | Cancel multiple by order ID |
259
+ | `bulk_cancel_by_cloid(cancels)` | Cancel multiple by client order ID |
260
+ | `schedule_cancel(time:)` | Dead man's switch cancel-all |
261
+
262
+ #### TWAP
263
+
264
+ | Method | Description |
265
+ |--------|-------------|
266
+ | `twap_order(coin, is_buy:, sz:, minutes:, ...)` | Place TWAP order |
267
+ | `twap_cancel(coin, twap_id:)` | Cancel TWAP order |
268
+
269
+ #### Account
270
+
271
+ | Method | Description |
272
+ |--------|-------------|
273
+ | `update_leverage(coin, leverage:, is_cross:)` | Set leverage |
274
+ | `update_isolated_margin(coin, is_buy:, amount:)` | Adjust isolated margin |
275
+ | `set_referrer(code)` | Set referral code |
276
+ | `set_expires_after(timestamp)` | Set action expiration |
277
+ | `approve_agent(name:)` | Approve agent, returns `[result, agent_key]` |
278
+ | `approve_builder_fee(builder:, max_fee_rate:)` | Approve builder fee |
279
+
280
+ #### Transfers
281
+
282
+ | Method | Description |
283
+ |--------|-------------|
284
+ | `usd_transfer(destination, amount:)` | Transfer USD |
285
+ | `spot_transfer(destination, token:, amount:)` | Transfer spot tokens |
286
+ | `withdraw_from_bridge(destination, amount:)` | Withdraw USDC to L1 |
287
+ | `usd_class_transfer(amount:, to_perp:)` | Move between perp and spot |
288
+ | `send_asset(destination, source_dex:, destination_dex:, token:, amount:)` | Cross-dex asset transfer |
289
+
290
+ #### Sub-accounts
291
+
292
+ | Method | Description |
293
+ |--------|-------------|
294
+ | `create_sub_account(name:)` | Create sub-account |
295
+ | `sub_account_transfer(sub_account_user:, is_deposit:, usd:)` | Sub-account USD transfer |
296
+ | `sub_account_spot_transfer(sub_account_user:, is_deposit:, token:, amount:)` | Sub-account spot transfer |
297
+
298
+ #### Vault
299
+
300
+ | Method | Description |
301
+ |--------|-------------|
302
+ | `vault_usd_transfer(vault_address:, is_deposit:, usd:)` | Vault USD deposit/withdraw |
303
+
304
+ #### Staking
305
+
306
+ | Method | Description |
307
+ |--------|-------------|
308
+ | `token_delegate(validator:, wei:, is_undelegate:)` | Delegate/undelegate tokens |
309
+
310
+ #### Multi-sig
311
+
312
+ | Method | Description |
313
+ |--------|-------------|
314
+ | `convert_to_multi_sig_user(authorized_users:, threshold:)` | Convert to multi-sig |
315
+ | `multi_sig(multi_sig_user, inner_action, signatures, nonce, ...)` | Execute multi-sig action |
316
+
317
+ #### Spot Deploy
318
+
319
+ | Method | Description |
320
+ |--------|-------------|
321
+ | `spot_deploy_register_token(token_name:, sz_decimals:, wei_decimals:, max_gas:, full_name:)` | Register token |
322
+ | `spot_deploy_user_genesis(token:, user_and_wei:, existing_token_and_wei:)` | User genesis |
323
+ | `spot_deploy_genesis(token, max_supply:, no_hyperliquidity:)` | Run genesis |
324
+ | `spot_deploy_register_spot(base_token:, quote_token:)` | Register trading pair |
325
+ | `spot_deploy_register_hyperliquidity(spot, start_px:, order_sz:, n_orders:, ...)` | Register liquidity |
326
+ | `spot_deploy_enable_freeze_privilege(token)` | Enable freeze |
327
+ | `spot_deploy_freeze_user(token, user:, freeze:)` | Freeze/unfreeze user |
328
+ | `spot_deploy_revoke_freeze_privilege(token)` | Revoke freeze |
329
+ | `spot_deploy_enable_quote_token(token)` | Enable quote token |
330
+ | `spot_deploy_set_deployer_trading_fee_share(token, share:)` | Set fee share |
331
+
332
+ #### Perp Deploy
333
+
334
+ | Method | Description |
335
+ |--------|-------------|
336
+ | `perp_deploy_register_asset(dex:, coin:, sz_decimals:, oracle_px:, ...)` | Register perp asset |
337
+ | `perp_deploy_set_oracle(dex:, oracle_pxs:, all_mark_pxs:, external_perp_pxs:)` | Set oracle prices |
338
+
339
+ #### Validator
340
+
341
+ | Method | Description |
342
+ |--------|-------------|
343
+ | `c_validator_register(node_ip:, name:, ...)` | Register validator |
344
+ | `c_validator_change_profile(unjailed:, ...)` | Update validator profile |
345
+ | `c_validator_unregister` | Unregister validator |
346
+ | `c_signer_unjail_self` | Unjail c-signer |
347
+ | `c_signer_jail_self` | Jail c-signer |
348
+
349
+ #### Other
350
+
351
+ | Method | Description |
352
+ |--------|-------------|
353
+ | `use_big_blocks(enable)` | Enable/disable big blocks |
354
+ | `agent_enable_dex_abstraction` | Enable dex abstraction for agent |
355
+ | `agent_set_abstraction(abstraction)` | Set agent abstraction level |
356
+ | `user_dex_abstraction(user, enabled:)` | Set user dex abstraction |
357
+ | `user_set_abstraction(user, abstraction:)` | Set user abstraction level |
358
+ | `noop(nonce)` | No-op (test signing) |
359
+
360
+ ### Order Types
361
+
362
+ ```ruby
363
+ # Good Till Cancel
364
+ { limit: { "tif" => "Gtc" } }
365
+
366
+ # Immediate or Cancel
367
+ { limit: { "tif" => "Ioc" } }
368
+
369
+ # Add Liquidity Only (Post Only)
370
+ { limit: { "tif" => "Alo" } }
371
+
372
+ # Stop Loss
373
+ { trigger: { triggerPx: 1700.0, isMarket: true, tpsl: "sl" } }
374
+
375
+ # Take Profit
376
+ { trigger: { triggerPx: 2000.0, isMarket: true, tpsl: "tp" } }
377
+ ```
378
+
379
+ ### Client Order IDs
380
+
381
+ ```ruby
382
+ cloid = Hyperliquid::Cloid.from_int(42)
383
+ # or
384
+ cloid = Hyperliquid::Cloid.from_str("0x0000000000000000000000000000002a")
385
+
386
+ exchange.order("ETH",
387
+ is_buy: true, sz: 1.0, limit_px: 1800.0,
388
+ order_type: { limit: { "tif" => "Gtc" } },
389
+ cloid: cloid
390
+ )
391
+
392
+ exchange.cancel_by_cloid("ETH", cloid)
393
+ ```
394
+
395
+ ### Builder Orders
396
+
397
+ ```ruby
398
+ exchange.order("ETH",
399
+ is_buy: true, sz: 1.0, limit_px: 1800.0,
400
+ order_type: { limit: { "tif" => "Gtc" } },
401
+ builder: { "b" => "0xbuilder...", "f" => 10 }
402
+ )
403
+ ```
404
+
405
+ ## Architecture
406
+
407
+ ```
408
+ lib/hyperliquid/
409
+ ├── version.rb # VERSION constant
410
+ ├── constants.rb # URLs, EIP-712 domains, user-signed types
411
+ ├── error.rb # Error hierarchy
412
+ ├── utils.rb # float_to_wire, order wire conversion
413
+ ├── cloid.rb # Client order ID value object
414
+ ├── transport.rb # Faraday HTTP client for /info and /exchange
415
+ ├── signer.rb # EIP-712 signing (L1 phantom agent + user-signed)
416
+ ├── websocket_manager.rb # WebSocket connection and subscription management
417
+ ├── info.rb # Read API (44 methods + WebSocket)
418
+ └── exchange.rb # Write API (54 methods)
419
+ ```
420
+
421
+ ### Signing
422
+
423
+ Two signing schemes, matching the official Python SDK exactly:
424
+
425
+ **L1 Actions** (orders, cancels, leverage — most trading operations):
426
+ msgpack(action) + nonce + vault flag → keccak256 → phantom agent → EIP-712 sign with `{chainId: 1337, name: "Exchange"}`
427
+
428
+ **User-Signed Actions** (transfers, withdrawals, staking — 11 action types):
429
+ EIP-712 sign with `{chainId: 0x66eee, name: "HyperliquidSignTransaction"}` and per-type field definitions
430
+
431
+ ## Development
432
+
433
+ ```bash
434
+ bundle install
435
+ rake test # 314 tests, 636 assertions
436
+ bundle exec rubocop # Lint
437
+ ```
438
+
439
+ ### Cross-Library Test Vectors
440
+
441
+ The test suite includes 141 test vectors generated by the official Python SDK (`hyperliquid-python-sdk` v0.22.0) to verify byte-identical output for:
442
+
443
+ - Float conversions (`float_to_wire`, `float_to_int_for_hashing`, `float_to_usd_int`)
444
+ - Order wire format and order type conversion
445
+ - Action hashing (all action types, with/without vault and expiration)
446
+ - L1 signatures (r, s, v for every action type on mainnet + testnet)
447
+ - User-signed signatures (r, s, v for all 11 types on mainnet + testnet)
448
+ - Phantom agent construction
449
+ - Order action assembly
450
+
451
+ To regenerate vectors:
452
+
453
+ ```bash
454
+ pip3 install hyperliquid-python-sdk
455
+ python3 test/generate_test_vectors.py
456
+ ```
457
+
458
+ ## License
459
+
460
+ MIT
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyperliquid
4
+ # Client Order ID: 128-bit hex string prefixed with "0x" (34 chars total).
5
+ class Cloid
6
+ attr_reader :raw
7
+
8
+ def initialize(raw)
9
+ unless raw.is_a?(String) && raw.match?(/\A0x[0-9a-f]{32}\z/)
10
+ raise ArgumentError, "Cloid must be 0x + 32 hex chars, got: #{raw.inspect}"
11
+ end
12
+
13
+ @raw = raw
14
+ end
15
+
16
+ # Create from integer.
17
+ def self.from_int(n)
18
+ new(format("0x%032x", n))
19
+ end
20
+
21
+ # Create from hex string.
22
+ def self.from_str(s)
23
+ new(s.downcase)
24
+ end
25
+
26
+ # Return the raw hex string for wire format.
27
+ def to_raw
28
+ @raw
29
+ end
30
+
31
+ def ==(other)
32
+ other.is_a?(Cloid) && @raw == other.raw
33
+ end
34
+ alias eql? ==
35
+
36
+ def hash
37
+ @raw.hash
38
+ end
39
+
40
+ def to_s
41
+ @raw
42
+ end
43
+
44
+ def inspect
45
+ "#<Hyperliquid::Cloid #{@raw}>"
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyperliquid
4
+ MAINNET_URL = "https://api.hyperliquid.xyz"
5
+ TESTNET_URL = "https://api.hyperliquid-testnet.xyz"
6
+
7
+ # EIP-712 domain for L1 action signing (phantom agent)
8
+ AGENT_EIP712_DOMAIN = {
9
+ name: "Exchange",
10
+ version: "1",
11
+ chainId: 1337,
12
+ verifyingContract: "0x0000000000000000000000000000000000000000"
13
+ }.freeze
14
+
15
+ AGENT_EIP712_TYPES = {
16
+ Agent: [
17
+ { name: "source", type: "string" },
18
+ { name: "connectionId", type: "bytes32" }
19
+ ]
20
+ }.freeze
21
+
22
+ # EIP-712 domain for user-signed actions (transfers, withdrawals)
23
+ # chainId is set dynamically from action's signatureChainId
24
+ USER_SIGNED_EIP712_DOMAIN_NAME = "HyperliquidSignTransaction"
25
+ USER_SIGNED_EIP712_DOMAIN_VERSION = "1"
26
+ USER_SIGNED_CHAIN_ID = 0x66eee # 421614 decimal
27
+
28
+ # User-signed action EIP-712 type definitions
29
+ USER_SIGNED_TYPES = {
30
+ "UsdSend" => [
31
+ { name: "hyperliquidChain", type: "string" },
32
+ { name: "destination", type: "string" },
33
+ { name: "amount", type: "string" },
34
+ { name: "time", type: "uint64" }
35
+ ],
36
+ "SpotSend" => [
37
+ { name: "hyperliquidChain", type: "string" },
38
+ { name: "destination", type: "string" },
39
+ { name: "token", type: "string" },
40
+ { name: "amount", type: "string" },
41
+ { name: "time", type: "uint64" }
42
+ ],
43
+ "Withdraw" => [
44
+ { name: "hyperliquidChain", type: "string" },
45
+ { name: "destination", type: "string" },
46
+ { name: "amount", type: "string" },
47
+ { name: "time", type: "uint64" }
48
+ ],
49
+ "UsdClassTransfer" => [
50
+ { name: "hyperliquidChain", type: "string" },
51
+ { name: "amount", type: "string" },
52
+ { name: "toPerp", type: "bool" },
53
+ { name: "nonce", type: "uint64" }
54
+ ],
55
+ "SendAsset" => [
56
+ { name: "hyperliquidChain", type: "string" },
57
+ { name: "destination", type: "string" },
58
+ { name: "sourceDex", type: "string" },
59
+ { name: "destinationDex", type: "string" },
60
+ { name: "token", type: "string" },
61
+ { name: "amount", type: "string" },
62
+ { name: "fromSubAccount", type: "string" },
63
+ { name: "nonce", type: "uint64" }
64
+ ],
65
+ "ApproveAgent" => [
66
+ { name: "hyperliquidChain", type: "string" },
67
+ { name: "agentAddress", type: "address" },
68
+ { name: "agentName", type: "string" },
69
+ { name: "nonce", type: "uint64" }
70
+ ],
71
+ "ApproveBuilderFee" => [
72
+ { name: "hyperliquidChain", type: "string" },
73
+ { name: "maxFeeRate", type: "string" },
74
+ { name: "builder", type: "address" },
75
+ { name: "nonce", type: "uint64" }
76
+ ],
77
+ "TokenDelegate" => [
78
+ { name: "hyperliquidChain", type: "string" },
79
+ { name: "validator", type: "address" },
80
+ { name: "wei", type: "uint64" },
81
+ { name: "isUndelegate", type: "bool" },
82
+ { name: "nonce", type: "uint64" }
83
+ ],
84
+ "ConvertToMultiSigUser" => [
85
+ { name: "hyperliquidChain", type: "string" },
86
+ { name: "signers", type: "string" },
87
+ { name: "nonce", type: "uint64" }
88
+ ],
89
+ "SendMultiSig" => [
90
+ { name: "hyperliquidChain", type: "string" },
91
+ { name: "multiSigActionHash", type: "bytes32" },
92
+ { name: "nonce", type: "uint64" }
93
+ ],
94
+ "UserDexAbstraction" => [
95
+ { name: "hyperliquidChain", type: "string" },
96
+ { name: "user", type: "address" },
97
+ { name: "enabled", type: "bool" },
98
+ { name: "nonce", type: "uint64" }
99
+ ],
100
+ "UserSetAbstraction" => [
101
+ { name: "hyperliquidChain", type: "string" },
102
+ { name: "user", type: "address" },
103
+ { name: "abstraction", type: "string" },
104
+ { name: "nonce", type: "uint64" }
105
+ ]
106
+ }.freeze
107
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyperliquid
4
+ class Error < StandardError; end
5
+
6
+ class ClientError < Error
7
+ attr_reader :status, :body
8
+
9
+ def initialize(message = nil, status: nil, body: nil)
10
+ @status = status
11
+ @body = body
12
+ super(message)
13
+ end
14
+ end
15
+
16
+ class ServerError < Error
17
+ attr_reader :status, :body
18
+
19
+ def initialize(message = nil, status: nil, body: nil)
20
+ @status = status
21
+ @body = body
22
+ super(message)
23
+ end
24
+ end
25
+
26
+ class SigningError < Error; end
27
+ end