hyperliquid 0.3.0 → 0.4.1
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 +4 -4
- data/.rubocop.yml +21 -2
- data/CHANGELOG.md +34 -4
- data/CLAUDE.md +202 -0
- data/README.md +19 -343
- data/docs/API.md +95 -0
- data/docs/CONFIGURATION.md +53 -0
- data/docs/DEVELOPMENT.md +54 -0
- data/docs/ERRORS.md +38 -0
- data/docs/EXAMPLES.md +342 -0
- data/example.rb +131 -28
- data/lib/hyperliquid/cloid.rb +102 -0
- data/lib/hyperliquid/constants.rb +1 -0
- data/lib/hyperliquid/exchange.rb +410 -0
- data/lib/hyperliquid/signing/eip712.rb +56 -0
- data/lib/hyperliquid/signing/signer.rb +122 -0
- data/lib/hyperliquid/version.rb +1 -1
- data/lib/hyperliquid.rb +33 -5
- data/test_integration.rb +255 -0
- metadata +40 -1
data/docs/EXAMPLES.md
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
## Info API
|
|
4
|
+
|
|
5
|
+
### General Info
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
# Retrieve mids for all coins
|
|
9
|
+
mids = sdk.info.all_mids
|
|
10
|
+
# => { "BTC" => "50000", "ETH" => "3000", ... }
|
|
11
|
+
|
|
12
|
+
user_address = "0x..."
|
|
13
|
+
|
|
14
|
+
# Retrieve a user's open orders
|
|
15
|
+
orders = sdk.info.open_orders(user_address)
|
|
16
|
+
# => [{ "coin" => "BTC", "sz" => "0.1", "px" => "50000", "side" => "A" }]
|
|
17
|
+
|
|
18
|
+
# Retrieve a user's open orders with additional frontend info
|
|
19
|
+
frontend_orders = sdk.info.frontend_open_orders(user_address)
|
|
20
|
+
# => [{ "coin" => "BTC", "isTrigger" => false, ... }]
|
|
21
|
+
|
|
22
|
+
# Retrieve a user's fills
|
|
23
|
+
fills = sdk.info.user_fills(user_address)
|
|
24
|
+
# => [{ "coin" => "BTC", "sz" => "0.1", "px" => "50000", "side" => "A", "time" => 1234567890 }]
|
|
25
|
+
|
|
26
|
+
# Retrieve a user's fills by time
|
|
27
|
+
start_time_ms = 1_700_000_000_000
|
|
28
|
+
end_time_ms = start_time_ms + 86_400_000
|
|
29
|
+
fills_by_time = sdk.info.user_fills_by_time(user_address, start_time_ms, end_time_ms)
|
|
30
|
+
# => [{ "coin" => "ETH", "px" => "3000", "time" => start_time_ms }, ...]
|
|
31
|
+
|
|
32
|
+
# Query user rate limits
|
|
33
|
+
rate_limit = sdk.info.user_rate_limit(user_address)
|
|
34
|
+
# => { "nRequestsUsed" => 100, "nRequestsCap" => 10000 }
|
|
35
|
+
|
|
36
|
+
# Query order status by oid
|
|
37
|
+
order_id = 12345
|
|
38
|
+
status_by_oid = sdk.info.order_status(user_address, order_id)
|
|
39
|
+
# => { "status" => "filled", ... }
|
|
40
|
+
|
|
41
|
+
# Query order status by cloid
|
|
42
|
+
cloid = "client-order-id-123"
|
|
43
|
+
status_by_cloid = sdk.info.order_status_by_cloid(user_address, cloid)
|
|
44
|
+
# => { "status" => "cancelled", ... }
|
|
45
|
+
|
|
46
|
+
# L2 order book snapshot
|
|
47
|
+
book = sdk.info.l2_book("BTC")
|
|
48
|
+
# => { "coin" => "BTC", "levels" => [[asks], [bids]], "time" => ... }
|
|
49
|
+
|
|
50
|
+
# Candle snapshot
|
|
51
|
+
candles = sdk.info.candles_snapshot("BTC", "1h", start_time_ms, end_time_ms)
|
|
52
|
+
# => [{ "t" => ..., "o" => "50000", "h" => "51000", "l" => "49000", "c" => "50500", "v" => "100" }]
|
|
53
|
+
|
|
54
|
+
# Check builder fee approval
|
|
55
|
+
builder_address = "0x..."
|
|
56
|
+
fee_approval = sdk.info.max_builder_fee(user_address, builder_address)
|
|
57
|
+
# => { "approved" => true, ... }
|
|
58
|
+
|
|
59
|
+
# Retrieve a user's historical orders
|
|
60
|
+
hist_orders = sdk.info.historical_orders(user_address)
|
|
61
|
+
# => [{ "oid" => 123, "coin" => "BTC", ... }]
|
|
62
|
+
hist_orders_ranged = sdk.info.historical_orders(user_address, start_time_ms, end_time_ms)
|
|
63
|
+
# => []
|
|
64
|
+
|
|
65
|
+
# Retrieve a user's TWAP slice fills
|
|
66
|
+
twap_fills = sdk.info.user_twap_slice_fills(user_address)
|
|
67
|
+
# => [{ "sliceId" => 1, "coin" => "ETH", "sz" => "1.0" }, ...]
|
|
68
|
+
twap_fills_ranged = sdk.info.user_twap_slice_fills(user_address, start_time_ms, end_time_ms)
|
|
69
|
+
# => []
|
|
70
|
+
|
|
71
|
+
# Retrieve a user's subaccounts
|
|
72
|
+
subaccounts = sdk.info.user_subaccounts(user_address)
|
|
73
|
+
# => ["0x1111...", ...]
|
|
74
|
+
|
|
75
|
+
# Retrieve details for a vault
|
|
76
|
+
vault_addr = "0x..."
|
|
77
|
+
vault = sdk.info.vault_details(vault_addr)
|
|
78
|
+
# => { "vaultAddress" => vault_addr, ... }
|
|
79
|
+
vault_with_user = sdk.info.vault_details(vault_addr, user_address)
|
|
80
|
+
# => { "vaultAddress" => vault_addr, "user" => user_address, ... }
|
|
81
|
+
|
|
82
|
+
# Retrieve a user's vault deposits
|
|
83
|
+
vault_deposits = sdk.info.user_vault_equities(user_address)
|
|
84
|
+
# => [{ "vaultAddress" => "0x...", "equity" => "123.45" }, ...]
|
|
85
|
+
|
|
86
|
+
# Query a user's role
|
|
87
|
+
role = sdk.info.user_role(user_address)
|
|
88
|
+
# => { "role" => "tradingUser" }
|
|
89
|
+
|
|
90
|
+
# Query a user's portfolio
|
|
91
|
+
portfolio = sdk.info.portfolio(user_address)
|
|
92
|
+
# => [["day", { "pnlHistory" => [...], "vlm" => "0.0" }], ...]
|
|
93
|
+
|
|
94
|
+
# Query a user's referral information
|
|
95
|
+
referral = sdk.info.referral(user_address)
|
|
96
|
+
# => { "referredBy" => { "referrer" => "0x..." }, ... }
|
|
97
|
+
|
|
98
|
+
# Query a user's fees
|
|
99
|
+
fees = sdk.info.user_fees(user_address)
|
|
100
|
+
# => { "userAddRate" => "0.0001", "feeSchedule" => { ... } }
|
|
101
|
+
|
|
102
|
+
# Query a user's staking delegations
|
|
103
|
+
delegations = sdk.info.delegations(user_address)
|
|
104
|
+
# => [{ "validator" => "0x...", "amount" => "100.0" }, ...]
|
|
105
|
+
|
|
106
|
+
# Query a user's staking summary
|
|
107
|
+
summary = sdk.info.delegator_summary(user_address)
|
|
108
|
+
# => { "delegated" => "12060.16529862", ... }
|
|
109
|
+
|
|
110
|
+
# Query a user's staking history
|
|
111
|
+
history = sdk.info.delegator_history(user_address)
|
|
112
|
+
# => [{ "time" => 1_736_726_400_073, "delta" => { ... } }, ...]
|
|
113
|
+
|
|
114
|
+
# Query a user's staking rewards
|
|
115
|
+
rewards = sdk.info.delegator_rewards(user_address)
|
|
116
|
+
# => [{ "time" => 1_736_726_400_073, "source" => "delegation", "totalAmount" => "0.123" }, ...]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Note:** `l2_book` and `candles_snapshot` work for both Perpetuals and Spot. For spot, use `"{BASE}/USDC"` when available (e.g., `"PURR/USDC"`). Otherwise, use the index alias `"@{index}"` from `spot_meta["universe"]`.
|
|
120
|
+
|
|
121
|
+
### Perpetuals
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
# Retrieve all perpetual DEXs
|
|
125
|
+
perp_dexs = sdk.info.perp_dexs
|
|
126
|
+
# => [nil, { "name" => "test", "full_name" => "test dex", ... }]
|
|
127
|
+
|
|
128
|
+
# Retrieve perpetuals metadata (optionally for a specific perp dex)
|
|
129
|
+
meta = sdk.info.meta
|
|
130
|
+
# => { "universe" => [...] }
|
|
131
|
+
meta = sdk.info.meta(dex: "perp-dex-name")
|
|
132
|
+
# => { "universe" => [...] }
|
|
133
|
+
|
|
134
|
+
# Retrieve perpetuals asset contexts (includes mark price, current funding, open interest, etc.)
|
|
135
|
+
meta_ctxs = sdk.info.meta_and_asset_ctxs
|
|
136
|
+
# => { "universe" => [...], "assetCtxs" => [...] }
|
|
137
|
+
|
|
138
|
+
# Retrieve user's perpetuals account summary (optionally for a specific perp dex)
|
|
139
|
+
state = sdk.info.user_state(user_address)
|
|
140
|
+
# => { "assetPositions" => [...], "marginSummary" => {...} }
|
|
141
|
+
state = sdk.info.user_state(user_address, dex: "perp-dex-name")
|
|
142
|
+
# => { "assetPositions" => [...], "marginSummary" => {...} }
|
|
143
|
+
|
|
144
|
+
# Retrieve a user's funding history or non-funding ledger updates (optional end_time)
|
|
145
|
+
funding = sdk.info.user_funding(user_address, start_time)
|
|
146
|
+
# => [{ "delta" => { "type" => "funding", ... }, "time" => ... }]
|
|
147
|
+
funding = sdk.info.user_funding(user_address, start_time, end_time)
|
|
148
|
+
# => [{ "delta" => { "type" => "funding", ... }, "time" => ... }]
|
|
149
|
+
|
|
150
|
+
# Retrieve historical funding rates
|
|
151
|
+
hist = sdk.info.funding_history("ETH", start_time)
|
|
152
|
+
# => [{ "coin" => "ETH", "fundingRate" => "...", "time" => ... }]
|
|
153
|
+
|
|
154
|
+
# Retrieve predicted funding rates for different venues
|
|
155
|
+
pred = sdk.info.predicted_fundings
|
|
156
|
+
# => [["AVAX", [["HlPerp", { "fundingRate" => "0.0000125", "nextFundingTime" => ... }], ...]], ...]
|
|
157
|
+
|
|
158
|
+
# Query perps at open interest caps
|
|
159
|
+
oi_capped = sdk.info.perps_at_open_interest_cap
|
|
160
|
+
# => ["BADGER", "CANTO", ...]
|
|
161
|
+
|
|
162
|
+
# Retrieve information about the Perp Deploy Auction
|
|
163
|
+
auction = sdk.info.perp_deploy_auction_status
|
|
164
|
+
# => { "startTimeSeconds" => ..., "durationSeconds" => ..., "startGas" => "500.0", ... }
|
|
165
|
+
|
|
166
|
+
# Retrieve User's Active Asset Data
|
|
167
|
+
aad = sdk.info.active_asset_data(user_address, "APT")
|
|
168
|
+
# => { "user" => user_address, "coin" => "APT", "leverage" => { "type" => "cross", "value" => 3 }, ... }
|
|
169
|
+
|
|
170
|
+
# Retrieve Builder-Deployed Perp Market Limits
|
|
171
|
+
limits = sdk.info.perp_dex_limits("builder-dex")
|
|
172
|
+
# => { "totalOiCap" => "10000000.0", "oiSzCapPerPerp" => "...", ... }
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Spot
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
# Retrieve spot metadata
|
|
179
|
+
spot_meta = sdk.info.spot_meta
|
|
180
|
+
# => { "tokens" => [...], "universe" => [...] }
|
|
181
|
+
|
|
182
|
+
# Retrieve spot asset contexts
|
|
183
|
+
spot_meta_ctxs = sdk.info.spot_meta_and_asset_ctxs
|
|
184
|
+
# => [ { "tokens" => [...], "universe" => [...] }, [ { "midPx" => "...", ... } ] ]
|
|
185
|
+
|
|
186
|
+
# Retrieve a user's token balances
|
|
187
|
+
balances = sdk.info.spot_balances(user_address)
|
|
188
|
+
# => { "balances" => [{ "coin" => "USDC", "token" => 0, "total" => "..." }, ...] }
|
|
189
|
+
|
|
190
|
+
# Retrieve information about the Spot Deploy Auction
|
|
191
|
+
deploy_state = sdk.info.spot_deploy_state(user_address)
|
|
192
|
+
# => { "states" => [...], "gasAuction" => { ... } }
|
|
193
|
+
|
|
194
|
+
# Retrieve information about the Spot Pair Deploy Auction
|
|
195
|
+
pair_status = sdk.info.spot_pair_deploy_auction_status
|
|
196
|
+
# => { "startTimeSeconds" => ..., "durationSeconds" => ..., "startGas" => "...", ... }
|
|
197
|
+
|
|
198
|
+
# Retrieve information about a token by onchain id in 34-character hexadecimal format
|
|
199
|
+
details = sdk.info.token_details("0x00000000000000000000000000000000")
|
|
200
|
+
# => { "name" => "TEST", "maxSupply" => "...", "midPx" => "...", ... }
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Exchange API (Trading)
|
|
204
|
+
|
|
205
|
+
### Basic Orders
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
# Initialize SDK with private key for trading
|
|
209
|
+
sdk = Hyperliquid.new(
|
|
210
|
+
testnet: true,
|
|
211
|
+
private_key: ENV['HYPERLIQUID_PRIVATE_KEY']
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Get wallet address
|
|
215
|
+
address = sdk.exchange.address
|
|
216
|
+
# => "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
|
|
217
|
+
|
|
218
|
+
# Place a limit buy order
|
|
219
|
+
result = sdk.exchange.order(
|
|
220
|
+
coin: 'BTC',
|
|
221
|
+
is_buy: true,
|
|
222
|
+
size: '0.01',
|
|
223
|
+
limit_px: '95000',
|
|
224
|
+
order_type: { limit: { tif: 'Gtc' } } # Good-til-canceled (default)
|
|
225
|
+
)
|
|
226
|
+
# => { "status" => "ok", "response" => { "type" => "order", "data" => { "statuses" => [...] } } }
|
|
227
|
+
|
|
228
|
+
# Place a limit sell order with client order ID
|
|
229
|
+
cloid = Hyperliquid::Cloid.from_int(123) # Or Cloid.random
|
|
230
|
+
result = sdk.exchange.order(
|
|
231
|
+
coin: 'ETH',
|
|
232
|
+
is_buy: false,
|
|
233
|
+
size: '0.5',
|
|
234
|
+
limit_px: '3500',
|
|
235
|
+
cloid: cloid
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Place a market order (IoC with slippage)
|
|
239
|
+
result = sdk.exchange.market_order(
|
|
240
|
+
coin: 'BTC',
|
|
241
|
+
is_buy: true,
|
|
242
|
+
size: '0.01',
|
|
243
|
+
slippage: 0.03 # 3% slippage tolerance (default: 5%)
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Place multiple orders at once
|
|
247
|
+
orders = [
|
|
248
|
+
{ coin: 'BTC', is_buy: true, size: '0.01', limit_px: '94000' },
|
|
249
|
+
{ coin: 'BTC', is_buy: false, size: '0.01', limit_px: '96000' }
|
|
250
|
+
]
|
|
251
|
+
result = sdk.exchange.bulk_orders(orders: orders)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Canceling Orders
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
# Cancel an order by order ID
|
|
258
|
+
oid = result.dig('response', 'data', 'statuses', 0, 'resting', 'oid')
|
|
259
|
+
sdk.exchange.cancel(coin: 'BTC', oid: oid)
|
|
260
|
+
|
|
261
|
+
# Cancel an order by client order ID
|
|
262
|
+
sdk.exchange.cancel_by_cloid(coin: 'ETH', cloid: cloid)
|
|
263
|
+
|
|
264
|
+
# Cancel multiple orders by order ID
|
|
265
|
+
cancels = [
|
|
266
|
+
{ coin: 'BTC', oid: 12345 },
|
|
267
|
+
{ coin: 'ETH', oid: 12346 }
|
|
268
|
+
]
|
|
269
|
+
sdk.exchange.bulk_cancel(cancels: cancels)
|
|
270
|
+
|
|
271
|
+
# Cancel multiple orders by client order ID
|
|
272
|
+
cloid_cancels = [
|
|
273
|
+
{ coin: 'BTC', cloid: Hyperliquid::Cloid.from_int(1) },
|
|
274
|
+
{ coin: 'ETH', cloid: Hyperliquid::Cloid.from_int(2) }
|
|
275
|
+
]
|
|
276
|
+
sdk.exchange.bulk_cancel_by_cloid(cancels: cloid_cancels)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Vault Trading
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
# Vault trading (trade on behalf of a vault)
|
|
283
|
+
vault_address = '0x...'
|
|
284
|
+
sdk.exchange.order(
|
|
285
|
+
coin: 'BTC',
|
|
286
|
+
is_buy: true,
|
|
287
|
+
size: '1.0',
|
|
288
|
+
limit_px: '95000',
|
|
289
|
+
vault_address: vault_address
|
|
290
|
+
)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Trigger Orders (Stop Loss / Take Profit)
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
# Stop loss: Sell when price drops to trigger level
|
|
297
|
+
sdk.exchange.order(
|
|
298
|
+
coin: 'BTC',
|
|
299
|
+
is_buy: false,
|
|
300
|
+
size: '0.1',
|
|
301
|
+
limit_px: '89900',
|
|
302
|
+
order_type: {
|
|
303
|
+
trigger: {
|
|
304
|
+
trigger_px: 90_000,
|
|
305
|
+
is_market: true, # Execute as market order when triggered
|
|
306
|
+
tpsl: 'sl' # Stop loss
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Take profit: Sell when price rises to trigger level
|
|
312
|
+
sdk.exchange.order(
|
|
313
|
+
coin: 'BTC',
|
|
314
|
+
is_buy: false,
|
|
315
|
+
size: '0.1',
|
|
316
|
+
limit_px: '100100',
|
|
317
|
+
order_type: {
|
|
318
|
+
trigger: {
|
|
319
|
+
trigger_px: 100_000,
|
|
320
|
+
is_market: false, # Execute as limit order when triggered
|
|
321
|
+
tpsl: 'tp' # Take profit
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
)
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Client Order IDs (Cloid)
|
|
328
|
+
|
|
329
|
+
```ruby
|
|
330
|
+
# Create from integer (zero-padded to 16 bytes)
|
|
331
|
+
cloid = Hyperliquid::Cloid.from_int(42)
|
|
332
|
+
# => "0x0000000000000000000000000000002a"
|
|
333
|
+
|
|
334
|
+
# Create from hex string
|
|
335
|
+
cloid = Hyperliquid::Cloid.from_str('0x1234567890abcdef1234567890abcdef')
|
|
336
|
+
|
|
337
|
+
# Create from UUID
|
|
338
|
+
cloid = Hyperliquid::Cloid.from_uuid('550e8400-e29b-41d4-a716-446655440000')
|
|
339
|
+
|
|
340
|
+
# Generate random
|
|
341
|
+
cloid = Hyperliquid::Cloid.random
|
|
342
|
+
```
|
data/example.rb
CHANGED
|
@@ -5,54 +5,157 @@ require_relative 'lib/hyperliquid'
|
|
|
5
5
|
|
|
6
6
|
# Example usage of the Hyperliquid Ruby SDK
|
|
7
7
|
|
|
8
|
+
puts 'Hyperliquid Ruby SDK v0.4.0 - API Examples'
|
|
9
|
+
puts '=' * 50
|
|
10
|
+
|
|
11
|
+
# =============================================================================
|
|
12
|
+
# INFO API (Read-only, no authentication required)
|
|
13
|
+
# =============================================================================
|
|
14
|
+
|
|
8
15
|
# Create a new SDK instance (defaults to mainnet)
|
|
9
16
|
sdk = Hyperliquid.new
|
|
10
17
|
|
|
11
|
-
puts
|
|
12
|
-
puts '=' * 50
|
|
18
|
+
puts "\n--- INFO API EXAMPLES ---\n"
|
|
13
19
|
|
|
14
20
|
# Example 1: Get all market mid prices
|
|
15
21
|
begin
|
|
16
|
-
puts
|
|
22
|
+
puts '1. Getting all market mid prices...'
|
|
17
23
|
mids = sdk.info.all_mids
|
|
18
|
-
puts "Found #{mids.length} markets" if mids.is_a?(Hash)
|
|
24
|
+
puts " Found #{mids.length} markets" if mids.is_a?(Hash)
|
|
25
|
+
puts " BTC mid: #{mids['BTC']}" if mids['BTC']
|
|
19
26
|
rescue Hyperliquid::Error => e
|
|
20
|
-
puts "Error
|
|
27
|
+
puts " Error: #{e.message}"
|
|
21
28
|
end
|
|
22
29
|
|
|
23
|
-
# Example 2: Get metadata for all assets
|
|
30
|
+
# Example 2: Get metadata for all perpetual assets
|
|
24
31
|
begin
|
|
25
|
-
puts "\n2. Getting asset metadata..."
|
|
32
|
+
puts "\n2. Getting perpetual asset metadata..."
|
|
26
33
|
meta = sdk.info.meta
|
|
27
|
-
|
|
34
|
+
universe = meta['universe'] || []
|
|
35
|
+
puts " Found #{universe.length} perpetual assets"
|
|
36
|
+
puts " First asset: #{universe.first['name']}" if universe.any?
|
|
37
|
+
rescue Hyperliquid::Error => e
|
|
38
|
+
puts " Error: #{e.message}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Example 3: Get L2 order book
|
|
42
|
+
begin
|
|
43
|
+
puts "\n3. Getting L2 order book for BTC..."
|
|
44
|
+
book = sdk.info.l2_book('BTC')
|
|
45
|
+
levels = book['levels'] || []
|
|
46
|
+
puts " Bid levels: #{levels[0]&.length || 0}, Ask levels: #{levels[1]&.length || 0}"
|
|
28
47
|
rescue Hyperliquid::Error => e
|
|
29
|
-
puts "Error
|
|
48
|
+
puts " Error: #{e.message}"
|
|
30
49
|
end
|
|
31
50
|
|
|
32
|
-
# Example
|
|
33
|
-
|
|
51
|
+
# Example 4: Get spot metadata
|
|
52
|
+
begin
|
|
53
|
+
puts "\n4. Getting spot asset metadata..."
|
|
54
|
+
spot_meta = sdk.info.spot_meta
|
|
55
|
+
tokens = spot_meta['tokens'] || []
|
|
56
|
+
puts " Found #{tokens.length} spot tokens"
|
|
57
|
+
rescue Hyperliquid::Error => e
|
|
58
|
+
puts " Error: #{e.message}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Example 5: Use testnet
|
|
62
|
+
puts "\n5. Creating testnet SDK..."
|
|
34
63
|
testnet_sdk = Hyperliquid.new(testnet: true)
|
|
35
|
-
puts "Testnet
|
|
64
|
+
puts " Testnet base URL: #{testnet_sdk.base_url}"
|
|
36
65
|
|
|
37
|
-
# Example
|
|
38
|
-
wallet_address = "0x#{'0' * 40}" #
|
|
66
|
+
# Example 6: User-specific endpoints (requires valid wallet address)
|
|
67
|
+
wallet_address = "0x#{'0' * 40}" # Placeholder address
|
|
39
68
|
|
|
40
69
|
begin
|
|
41
|
-
puts "\
|
|
42
|
-
|
|
43
|
-
puts "
|
|
70
|
+
puts "\n6. Getting user state for #{wallet_address[0..13]}..."
|
|
71
|
+
state = sdk.info.user_state(wallet_address)
|
|
72
|
+
puts " Account value: #{state.dig('marginSummary', 'accountValue') || 'N/A'}"
|
|
44
73
|
rescue Hyperliquid::Error => e
|
|
45
|
-
puts "Error
|
|
74
|
+
puts " Error: #{e.message}"
|
|
46
75
|
end
|
|
47
76
|
|
|
77
|
+
# =============================================================================
|
|
78
|
+
# EXCHANGE API (Authenticated, requires private key)
|
|
79
|
+
# =============================================================================
|
|
80
|
+
|
|
81
|
+
puts "\n--- EXCHANGE API EXAMPLES ---\n"
|
|
82
|
+
puts "(These examples are commented out to prevent accidental execution)\n"
|
|
83
|
+
|
|
84
|
+
# To use the Exchange API, create an SDK with your private key:
|
|
85
|
+
#
|
|
86
|
+
# sdk = Hyperliquid.new(
|
|
87
|
+
# testnet: true, # Use testnet for testing!
|
|
88
|
+
# private_key: ENV['HYPERLIQUID_PRIVATE_KEY']
|
|
89
|
+
# )
|
|
90
|
+
#
|
|
91
|
+
# Your wallet address:
|
|
92
|
+
# puts sdk.exchange.address
|
|
93
|
+
#
|
|
94
|
+
# Place a limit order:
|
|
95
|
+
# result = sdk.exchange.order(
|
|
96
|
+
# coin: 'BTC',
|
|
97
|
+
# is_buy: true,
|
|
98
|
+
# size: 0.001,
|
|
99
|
+
# limit_px: 50000.0,
|
|
100
|
+
# order_type: { limit: { tif: 'Gtc' } },
|
|
101
|
+
# reduce_only: false
|
|
102
|
+
# )
|
|
103
|
+
#
|
|
104
|
+
# Place a market order (uses slippage):
|
|
105
|
+
# result = sdk.exchange.market_order(
|
|
106
|
+
# coin: 'BTC',
|
|
107
|
+
# is_buy: true,
|
|
108
|
+
# size: 0.001,
|
|
109
|
+
# slippage: 0.01 # 1% slippage
|
|
110
|
+
# )
|
|
111
|
+
#
|
|
112
|
+
# Place a stop loss order:
|
|
113
|
+
# result = sdk.exchange.order(
|
|
114
|
+
# coin: 'BTC',
|
|
115
|
+
# is_buy: false,
|
|
116
|
+
# size: 0.001,
|
|
117
|
+
# limit_px: 48000.0,
|
|
118
|
+
# order_type: {
|
|
119
|
+
# trigger: {
|
|
120
|
+
# trigger_px: 49000.0,
|
|
121
|
+
# is_market: false,
|
|
122
|
+
# tpsl: 'sl'
|
|
123
|
+
# }
|
|
124
|
+
# },
|
|
125
|
+
# reduce_only: true
|
|
126
|
+
# )
|
|
127
|
+
#
|
|
128
|
+
# Cancel an order by OID:
|
|
129
|
+
# result = sdk.exchange.cancel(coin: 'BTC', oid: 123456789)
|
|
130
|
+
#
|
|
131
|
+
# Cancel an order by client order ID:
|
|
132
|
+
# result = sdk.exchange.cancel_by_cloid(coin: 'BTC', cloid: '0x...')
|
|
133
|
+
#
|
|
134
|
+
# Bulk cancel multiple orders:
|
|
135
|
+
# result = sdk.exchange.bulk_cancel([
|
|
136
|
+
# { coin: 'BTC', oid: 123 },
|
|
137
|
+
# { coin: 'ETH', oid: 456 }
|
|
138
|
+
# ])
|
|
139
|
+
#
|
|
140
|
+
# Use client order IDs for tracking:
|
|
141
|
+
# cloid = Hyperliquid::Cloid.random
|
|
142
|
+
# result = sdk.exchange.order(
|
|
143
|
+
# coin: 'BTC',
|
|
144
|
+
# is_buy: true,
|
|
145
|
+
# size: 0.001,
|
|
146
|
+
# limit_px: 50000.0,
|
|
147
|
+
# cloid: cloid
|
|
148
|
+
# )
|
|
149
|
+
# puts "Order placed with cloid: #{cloid}"
|
|
150
|
+
#
|
|
151
|
+
# Trade on behalf of a vault:
|
|
152
|
+
# result = sdk.exchange.order(
|
|
153
|
+
# coin: 'BTC',
|
|
154
|
+
# is_buy: true,
|
|
155
|
+
# size: 0.001,
|
|
156
|
+
# limit_px: 50000.0,
|
|
157
|
+
# vault_address: '0x...'
|
|
158
|
+
# )
|
|
159
|
+
|
|
160
|
+
puts 'See commented code above for Exchange API usage patterns.'
|
|
48
161
|
puts "\nSDK examples completed!"
|
|
49
|
-
puts "\nAvailable Info methods:"
|
|
50
|
-
puts '- all_mids() - Get all market mid prices'
|
|
51
|
-
puts "- open_orders(user) - Get user's open orders"
|
|
52
|
-
puts "- user_fills(user) - Get user's fill history"
|
|
53
|
-
puts '- order_status(user, oid) - Get order status'
|
|
54
|
-
puts "- user_state(user) - Get user's trading state"
|
|
55
|
-
puts '- meta() - Get asset metadata'
|
|
56
|
-
puts '- meta_and_asset_ctxs() - Get extended asset metadata'
|
|
57
|
-
puts '- l2_book(coin) - Get L2 order book'
|
|
58
|
-
puts '- candles_snapshot(coin, interval, start_time, end_time) - Get candlestick data'
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Hyperliquid
|
|
6
|
+
# Client Order ID for tracking orders
|
|
7
|
+
# Must be a 16-byte hex string in format: 0x + 32 hex characters
|
|
8
|
+
class Cloid
|
|
9
|
+
# Initialize a new Cloid from a raw hex string
|
|
10
|
+
# @param raw_cloid [String] Hex string in format 0x + 32 hex characters
|
|
11
|
+
# @raise [ArgumentError] If format is invalid
|
|
12
|
+
def initialize(raw_cloid)
|
|
13
|
+
@raw_cloid = raw_cloid.downcase
|
|
14
|
+
validate!
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Get the raw hex string representation
|
|
18
|
+
# @return [String] The raw cloid string
|
|
19
|
+
def to_raw
|
|
20
|
+
@raw_cloid
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# String representation
|
|
24
|
+
# @return [String] The raw cloid string
|
|
25
|
+
def to_s
|
|
26
|
+
@raw_cloid
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Inspect representation
|
|
30
|
+
# @return [String] The raw cloid string
|
|
31
|
+
def inspect
|
|
32
|
+
@raw_cloid
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Equality check
|
|
36
|
+
# @param other [Cloid, String] Another Cloid or string to compare
|
|
37
|
+
# @return [Boolean] True if equal
|
|
38
|
+
def ==(other)
|
|
39
|
+
case other
|
|
40
|
+
when Cloid
|
|
41
|
+
@raw_cloid == other.to_raw
|
|
42
|
+
when String
|
|
43
|
+
@raw_cloid == other.downcase
|
|
44
|
+
else
|
|
45
|
+
false
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
alias eql? ==
|
|
50
|
+
|
|
51
|
+
# Hash for use in Hash keys
|
|
52
|
+
# @return [Integer] Hash value
|
|
53
|
+
def hash
|
|
54
|
+
@raw_cloid.hash
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class << self
|
|
58
|
+
# Create a Cloid from an integer
|
|
59
|
+
# @param value [Integer] Integer value (0 to 2^128-1)
|
|
60
|
+
# @return [Cloid] New Cloid instance
|
|
61
|
+
# @raise [ArgumentError] If value is out of range
|
|
62
|
+
def from_int(value)
|
|
63
|
+
raise ArgumentError, 'cloid integer must be non-negative' if value.negative?
|
|
64
|
+
raise ArgumentError, 'cloid integer must be less than 2^128' if value >= 2**128
|
|
65
|
+
|
|
66
|
+
new(format('0x%032x', value))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Create a Cloid from a hex string
|
|
70
|
+
# @param value [String] Hex string in format 0x + 32 hex characters
|
|
71
|
+
# @return [Cloid] New Cloid instance
|
|
72
|
+
def from_str(value)
|
|
73
|
+
new(value)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Generate a random Cloid
|
|
77
|
+
# @return [Cloid] New random Cloid instance
|
|
78
|
+
def random
|
|
79
|
+
from_int(SecureRandom.random_number(2**128))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Create a Cloid from a UUID string
|
|
83
|
+
# @param uuid [String] UUID string (with or without dashes)
|
|
84
|
+
# @return [Cloid] New Cloid instance
|
|
85
|
+
def from_uuid(uuid)
|
|
86
|
+
hex = uuid.delete('-').downcase
|
|
87
|
+
raise ArgumentError, 'UUID must be 32 hex characters' unless hex.match?(/\A[0-9a-f]{32}\z/)
|
|
88
|
+
|
|
89
|
+
new("0x#{hex}")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def validate!
|
|
96
|
+
return if @raw_cloid.match?(/\A0x[0-9a-f]{32}\z/)
|
|
97
|
+
|
|
98
|
+
raise ArgumentError,
|
|
99
|
+
"cloid must be '0x' followed by 32 hex characters (16 bytes). Got: #{@raw_cloid.inspect}"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|