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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +460 -0
- data/lib/hyperliquid/cloid.rb +48 -0
- data/lib/hyperliquid/constants.rb +107 -0
- data/lib/hyperliquid/error.rb +27 -0
- data/lib/hyperliquid/exchange.rb +840 -0
- data/lib/hyperliquid/info.rb +486 -0
- data/lib/hyperliquid/signer.rb +147 -0
- data/lib/hyperliquid/transport.rb +69 -0
- data/lib/hyperliquid/utils.rb +78 -0
- data/lib/hyperliquid/version.rb +5 -0
- data/lib/hyperliquid/websocket_manager.rb +197 -0
- data/lib/hyperliquid.rb +15 -0
- metadata +114 -0
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
|