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.
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 'Hyperliquid Ruby SDK v0.1 - Info API Examples'
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 "\n1. Getting all market mid prices..."
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 getting market mids: #{e.message}"
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
- puts 'Got metadata for universe' if meta.is_a?(Hash)
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 getting metadata: #{e.message}"
48
+ puts " Error: #{e.message}"
30
49
  end
31
50
 
32
- # Example 3: Use testnet
33
- puts "\n3. Using testnet..."
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 SDK created, base URL: #{testnet_sdk.base_url}"
64
+ puts " Testnet base URL: #{testnet_sdk.base_url}"
36
65
 
37
- # Example 4: User-specific endpoints (requires valid wallet address)
38
- wallet_address = "0x#{'0' * 40}" # Example placeholder address
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 "\n4. Getting open orders for wallet #{wallet_address[0..10]}..."
42
- orders = sdk.info.open_orders(wallet_address)
43
- puts "Open orders: #{orders}"
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 getting open orders: #{e.message}"
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
@@ -9,6 +9,7 @@ module Hyperliquid
9
9
 
10
10
  # API endpoints
11
11
  INFO_ENDPOINT = '/info'
12
+ EXCHANGE_ENDPOINT = '/exchange'
12
13
 
13
14
  # Request timeouts (seconds)
14
15
  DEFAULT_TIMEOUT = 30