hyperliquid 0.5.0 → 0.6.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 +4 -4
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +13 -1
- data/CLAUDE.md +6 -7
- data/docs/API.md +14 -1
- data/docs/DEVELOPMENT.md +31 -17
- data/docs/EXAMPLES.md +91 -0
- data/lib/hyperliquid/exchange.rb +195 -0
- data/lib/hyperliquid/signing/eip712.rb +73 -0
- data/lib/hyperliquid/signing/signer.rb +27 -2
- data/lib/hyperliquid/version.rb +1 -1
- data/scripts/test_01_spot_market_roundtrip.rb +48 -0
- data/scripts/test_02_spot_limit_order.rb +48 -0
- data/scripts/test_03_perp_market_roundtrip.rb +52 -0
- data/scripts/test_04_perp_limit_order.rb +52 -0
- data/scripts/test_05_update_leverage.rb +39 -0
- data/scripts/test_06_modify_order.rb +67 -0
- data/scripts/test_07_market_close.rb +49 -0
- data/scripts/test_08_usd_class_transfer.rb +23 -0
- data/scripts/test_09_sub_account_lifecycle.rb +51 -0
- data/scripts/test_10_vault_transfer.rb +41 -0
- data/scripts/test_all.rb +86 -0
- data/scripts/test_helpers.rb +100 -0
- data/test_integration.rb +8 -367
- metadata +13 -1
data/test_integration.rb
CHANGED
|
@@ -1,376 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
# Hyperliquid Ruby SDK - Testnet Integration
|
|
4
|
+
# Hyperliquid Ruby SDK - Testnet Integration Tests
|
|
5
5
|
#
|
|
6
|
-
# This
|
|
7
|
-
#
|
|
8
|
-
# 2. Spot limit order (place and cancel)
|
|
9
|
-
# 3. Perp market roundtrip (long BTC, close position)
|
|
10
|
-
# 4. Perp limit order (place short, cancel)
|
|
11
|
-
#
|
|
12
|
-
# Prerequisites:
|
|
13
|
-
# - Testnet wallet with USDC balance
|
|
14
|
-
# - Get testnet funds from: https://app.hyperliquid-testnet.xyz
|
|
6
|
+
# This is a convenience wrapper that runs all integration tests.
|
|
7
|
+
# Individual tests live in scripts/ and can be run standalone.
|
|
15
8
|
#
|
|
16
9
|
# Usage:
|
|
17
10
|
# HYPERLIQUID_PRIVATE_KEY=0x... ruby test_integration.rb
|
|
18
11
|
#
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
WAIT_SECONDS = 10
|
|
25
|
-
SPOT_SLIPPAGE = 0.40 # 40% for illiquid testnet spot markets
|
|
26
|
-
PERP_SLIPPAGE = 0.05 # 5% for perp markets
|
|
27
|
-
|
|
28
|
-
def separator(title)
|
|
29
|
-
puts
|
|
30
|
-
puts '=' * 60
|
|
31
|
-
puts title
|
|
32
|
-
puts '=' * 60
|
|
33
|
-
puts
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def wait_with_countdown(seconds, message)
|
|
37
|
-
puts message
|
|
38
|
-
seconds.downto(1) do |i|
|
|
39
|
-
print "\r #{i} seconds remaining... "
|
|
40
|
-
sleep 1
|
|
41
|
-
end
|
|
42
|
-
puts "\r Done! "
|
|
43
|
-
puts
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def check_result(result, operation)
|
|
47
|
-
status = result.dig('response', 'data', 'statuses', 0)
|
|
48
|
-
|
|
49
|
-
if status.is_a?(Hash) && status['error']
|
|
50
|
-
puts "FAILED: #{status['error']}"
|
|
51
|
-
return false
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
if status.is_a?(Hash) && status['resting']
|
|
55
|
-
puts "Order resting with OID: #{status['resting']['oid']}"
|
|
56
|
-
return status['resting']['oid']
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
if status == 'success' || (status.is_a?(Hash) && status['filled'])
|
|
60
|
-
puts "#{operation} successful!"
|
|
61
|
-
return true
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
puts "Result: #{status.inspect}"
|
|
65
|
-
true
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# --- Main Script ---
|
|
69
|
-
|
|
70
|
-
private_key = ENV['HYPERLIQUID_PRIVATE_KEY']
|
|
71
|
-
unless private_key
|
|
72
|
-
puts 'Error: Set HYPERLIQUID_PRIVATE_KEY environment variable'
|
|
73
|
-
puts 'Usage: HYPERLIQUID_PRIVATE_KEY=0x... ruby test_integration.rb'
|
|
74
|
-
exit 1
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
sdk = Hyperliquid.new(
|
|
78
|
-
testnet: true,
|
|
79
|
-
private_key: private_key
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
puts 'Hyperliquid Ruby SDK - Testnet Integration Test'
|
|
83
|
-
puts '=' * 60
|
|
84
|
-
puts "Wallet: #{sdk.exchange.address}"
|
|
85
|
-
puts 'Network: Testnet'
|
|
86
|
-
puts "Testnet UI: https://app.hyperliquid-testnet.xyz"
|
|
87
|
-
|
|
88
|
-
# ============================================================
|
|
89
|
-
# TEST 1: Spot Market Roundtrip (PURR/USDC)
|
|
90
|
-
# ============================================================
|
|
91
|
-
separator('TEST 1: Spot Market Roundtrip (PURR/USDC)')
|
|
92
|
-
|
|
93
|
-
spot_coin = 'PURR/USDC'
|
|
94
|
-
spot_size = 5 # PURR has 0 decimals
|
|
95
|
-
|
|
96
|
-
mids = sdk.info.all_mids
|
|
97
|
-
spot_price = mids[spot_coin]&.to_f
|
|
98
|
-
|
|
99
|
-
if spot_price&.positive?
|
|
100
|
-
puts "#{spot_coin} mid: $#{spot_price}"
|
|
101
|
-
puts "Size: #{spot_size} PURR (~$#{(spot_size * spot_price).round(2)})"
|
|
102
|
-
puts "Slippage: #{(SPOT_SLIPPAGE * 100).to_i}%"
|
|
103
|
-
puts
|
|
104
|
-
|
|
105
|
-
# Buy
|
|
106
|
-
puts 'Placing market BUY...'
|
|
107
|
-
result = sdk.exchange.market_order(
|
|
108
|
-
coin: spot_coin,
|
|
109
|
-
is_buy: true,
|
|
110
|
-
size: spot_size,
|
|
111
|
-
slippage: SPOT_SLIPPAGE
|
|
112
|
-
)
|
|
113
|
-
check_result(result, 'Buy')
|
|
114
|
-
|
|
115
|
-
wait_with_countdown(WAIT_SECONDS, 'Waiting before sell...')
|
|
116
|
-
|
|
117
|
-
# Sell
|
|
118
|
-
puts 'Placing market SELL...'
|
|
119
|
-
result = sdk.exchange.market_order(
|
|
120
|
-
coin: spot_coin,
|
|
121
|
-
is_buy: false,
|
|
122
|
-
size: spot_size,
|
|
123
|
-
slippage: SPOT_SLIPPAGE
|
|
124
|
-
)
|
|
125
|
-
check_result(result, 'Sell')
|
|
126
|
-
else
|
|
127
|
-
puts "SKIPPED: Could not get #{spot_coin} price"
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# ============================================================
|
|
131
|
-
# TEST 2: Spot Limit Order (Place and Cancel)
|
|
132
|
-
# ============================================================
|
|
133
|
-
separator('TEST 2: Spot Limit Order (Place and Cancel)')
|
|
134
|
-
|
|
135
|
-
if spot_price&.positive?
|
|
136
|
-
# Place limit buy well below market (won't fill)
|
|
137
|
-
limit_price = (spot_price * 0.50).round(2) # 50% below mid
|
|
138
|
-
puts "#{spot_coin} mid: $#{spot_price}"
|
|
139
|
-
puts "Limit price: $#{limit_price} (50% below mid - won't fill)"
|
|
140
|
-
puts "Size: #{spot_size} PURR"
|
|
141
|
-
puts
|
|
142
|
-
|
|
143
|
-
puts 'Placing limit BUY order...'
|
|
144
|
-
result = sdk.exchange.order(
|
|
145
|
-
coin: spot_coin,
|
|
146
|
-
is_buy: true,
|
|
147
|
-
size: spot_size,
|
|
148
|
-
limit_px: limit_price,
|
|
149
|
-
order_type: { limit: { tif: 'Gtc' } },
|
|
150
|
-
reduce_only: false
|
|
151
|
-
)
|
|
152
|
-
oid = check_result(result, 'Limit order')
|
|
153
|
-
|
|
154
|
-
if oid.is_a?(Integer)
|
|
155
|
-
wait_with_countdown(WAIT_SECONDS, 'Order resting. Waiting before cancel...')
|
|
156
|
-
|
|
157
|
-
puts "Canceling order #{oid}..."
|
|
158
|
-
result = sdk.exchange.cancel(coin: spot_coin, oid: oid)
|
|
159
|
-
check_result(result, 'Cancel')
|
|
160
|
-
end
|
|
161
|
-
else
|
|
162
|
-
puts "SKIPPED: Could not get #{spot_coin} price"
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# ============================================================
|
|
166
|
-
# TEST 3: Perp Market Roundtrip (BTC Long)
|
|
167
|
-
# ============================================================
|
|
168
|
-
separator('TEST 3: Perp Market Roundtrip (BTC Long)')
|
|
169
|
-
|
|
170
|
-
perp_coin = 'BTC'
|
|
171
|
-
btc_price = mids[perp_coin]&.to_f
|
|
172
|
-
|
|
173
|
-
if btc_price&.positive?
|
|
174
|
-
# Get BTC metadata for size precision
|
|
175
|
-
meta = sdk.info.meta
|
|
176
|
-
btc_meta = meta['universe'].find { |a| a['name'] == perp_coin }
|
|
177
|
-
sz_decimals = btc_meta['szDecimals']
|
|
178
|
-
|
|
179
|
-
# Calculate size for ~$20 notional
|
|
180
|
-
perp_size = (20.0 / btc_price).ceil(sz_decimals)
|
|
181
|
-
|
|
182
|
-
puts "#{perp_coin} mid: $#{btc_price.round(2)}"
|
|
183
|
-
puts "Size: #{perp_size} BTC (~$#{(perp_size * btc_price).round(2)})"
|
|
184
|
-
puts "Slippage: #{(PERP_SLIPPAGE * 100).to_i}%"
|
|
185
|
-
puts
|
|
186
|
-
|
|
187
|
-
# Open long
|
|
188
|
-
puts 'Opening LONG position (market buy)...'
|
|
189
|
-
result = sdk.exchange.market_order(
|
|
190
|
-
coin: perp_coin,
|
|
191
|
-
is_buy: true,
|
|
192
|
-
size: perp_size,
|
|
193
|
-
slippage: PERP_SLIPPAGE
|
|
194
|
-
)
|
|
195
|
-
check_result(result, 'Long open')
|
|
196
|
-
|
|
197
|
-
wait_with_countdown(WAIT_SECONDS, 'Position open. Waiting before close...')
|
|
198
|
-
|
|
199
|
-
# Close long (sell to close)
|
|
200
|
-
puts 'Closing LONG position (market sell)...'
|
|
201
|
-
result = sdk.exchange.market_order(
|
|
202
|
-
coin: perp_coin,
|
|
203
|
-
is_buy: false,
|
|
204
|
-
size: perp_size,
|
|
205
|
-
slippage: PERP_SLIPPAGE
|
|
206
|
-
)
|
|
207
|
-
check_result(result, 'Long close')
|
|
208
|
-
else
|
|
209
|
-
puts "SKIPPED: Could not get #{perp_coin} price"
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
# ============================================================
|
|
213
|
-
# TEST 4: Perp Limit Order (Short, then Cancel)
|
|
214
|
-
# ============================================================
|
|
215
|
-
separator('TEST 4: Perp Limit Order (Short, then Cancel)')
|
|
216
|
-
|
|
217
|
-
if btc_price&.positive?
|
|
218
|
-
# Place limit sell well above market (won't fill)
|
|
219
|
-
limit_price = (btc_price * 1.50).round(0).to_i # 50% above mid, whole number tick
|
|
220
|
-
perp_size = (20.0 / btc_price).ceil(sz_decimals)
|
|
221
|
-
|
|
222
|
-
puts "#{perp_coin} mid: $#{btc_price.round(2)}"
|
|
223
|
-
puts "Limit price: $#{limit_price} (50% above mid - won't fill)"
|
|
224
|
-
puts "Size: #{perp_size} BTC"
|
|
225
|
-
puts
|
|
226
|
-
|
|
227
|
-
puts 'Placing limit SELL order (short)...'
|
|
228
|
-
result = sdk.exchange.order(
|
|
229
|
-
coin: perp_coin,
|
|
230
|
-
is_buy: false,
|
|
231
|
-
size: perp_size,
|
|
232
|
-
limit_px: limit_price,
|
|
233
|
-
order_type: { limit: { tif: 'Gtc' } },
|
|
234
|
-
reduce_only: false
|
|
235
|
-
)
|
|
236
|
-
oid = check_result(result, 'Limit short')
|
|
237
|
-
|
|
238
|
-
if oid.is_a?(Integer)
|
|
239
|
-
wait_with_countdown(WAIT_SECONDS, 'Order resting. Waiting before cancel...')
|
|
240
|
-
|
|
241
|
-
puts "Canceling order #{oid}..."
|
|
242
|
-
result = sdk.exchange.cancel(coin: perp_coin, oid: oid)
|
|
243
|
-
check_result(result, 'Cancel')
|
|
244
|
-
end
|
|
245
|
-
else
|
|
246
|
-
puts "SKIPPED: Could not get #{perp_coin} price"
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
# ============================================================
|
|
250
|
-
# TEST 5: Update Leverage (BTC)
|
|
251
|
-
# ============================================================
|
|
252
|
-
separator('TEST 5: Update Leverage (BTC)')
|
|
253
|
-
|
|
254
|
-
if btc_price&.positive?
|
|
255
|
-
puts 'Setting BTC to 5x cross leverage...'
|
|
256
|
-
result = sdk.exchange.update_leverage(coin: perp_coin, leverage: 5, is_cross: true)
|
|
257
|
-
puts "Result: #{result.inspect}"
|
|
258
|
-
puts
|
|
259
|
-
|
|
260
|
-
wait_with_countdown(WAIT_SECONDS, 'Waiting before next leverage update...')
|
|
261
|
-
|
|
262
|
-
puts 'Setting BTC to 3x isolated leverage...'
|
|
263
|
-
result = sdk.exchange.update_leverage(coin: perp_coin, leverage: 3, is_cross: false)
|
|
264
|
-
puts "Result: #{result.inspect}"
|
|
265
|
-
puts
|
|
266
|
-
|
|
267
|
-
wait_with_countdown(WAIT_SECONDS, 'Waiting before resetting leverage...')
|
|
268
|
-
|
|
269
|
-
puts 'Resetting BTC to 1x cross leverage...'
|
|
270
|
-
result = sdk.exchange.update_leverage(coin: perp_coin, leverage: 1, is_cross: true)
|
|
271
|
-
puts "Result: #{result.inspect}"
|
|
272
|
-
else
|
|
273
|
-
puts "SKIPPED: Could not get #{perp_coin} price"
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
# ============================================================
|
|
277
|
-
# TEST 6: Modify Order (BTC)
|
|
278
|
-
# ============================================================
|
|
279
|
-
separator('TEST 6: Modify Order (BTC)')
|
|
280
|
-
|
|
281
|
-
if btc_price&.positive?
|
|
282
|
-
# Place limit buy well below market (won't fill)
|
|
283
|
-
original_price = (btc_price * 0.50).round(0).to_i
|
|
284
|
-
modified_price = (btc_price * 0.51).round(0).to_i
|
|
285
|
-
perp_size = (20.0 / btc_price).ceil(sz_decimals)
|
|
286
|
-
|
|
287
|
-
puts "#{perp_coin} mid: $#{btc_price.round(2)}"
|
|
288
|
-
puts "Original limit: $#{original_price} (50% below mid)"
|
|
289
|
-
puts "Modified limit: $#{modified_price} (49% below mid)"
|
|
290
|
-
puts "Size: #{perp_size} BTC"
|
|
291
|
-
puts
|
|
292
|
-
|
|
293
|
-
puts 'Placing limit BUY order...'
|
|
294
|
-
result = sdk.exchange.order(
|
|
295
|
-
coin: perp_coin,
|
|
296
|
-
is_buy: true,
|
|
297
|
-
size: perp_size,
|
|
298
|
-
limit_px: original_price,
|
|
299
|
-
order_type: { limit: { tif: 'Gtc' } }
|
|
300
|
-
)
|
|
301
|
-
oid = check_result(result, 'Limit buy')
|
|
302
|
-
|
|
303
|
-
if oid.is_a?(Integer)
|
|
304
|
-
wait_with_countdown(WAIT_SECONDS, 'Order resting. Waiting before modify...')
|
|
305
|
-
|
|
306
|
-
puts "Modifying order #{oid} (price: $#{original_price} -> $#{modified_price})..."
|
|
307
|
-
result = sdk.exchange.modify_order(
|
|
308
|
-
oid: oid,
|
|
309
|
-
coin: perp_coin,
|
|
310
|
-
is_buy: true,
|
|
311
|
-
size: perp_size,
|
|
312
|
-
limit_px: modified_price
|
|
313
|
-
)
|
|
314
|
-
puts "Modify result: #{result.inspect}"
|
|
315
|
-
puts
|
|
316
|
-
|
|
317
|
-
# Extract new oid from modify result if available
|
|
318
|
-
new_status = result.dig('response', 'data', 'statuses', 0)
|
|
319
|
-
new_oid = if new_status.is_a?(Hash) && new_status['resting']
|
|
320
|
-
new_status['resting']['oid']
|
|
321
|
-
else
|
|
322
|
-
oid
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
wait_with_countdown(WAIT_SECONDS, 'Waiting before cancel...')
|
|
326
|
-
|
|
327
|
-
puts "Canceling modified order #{new_oid}..."
|
|
328
|
-
result = sdk.exchange.cancel(coin: perp_coin, oid: new_oid)
|
|
329
|
-
check_result(result, 'Cancel')
|
|
330
|
-
end
|
|
331
|
-
else
|
|
332
|
-
puts "SKIPPED: Could not get #{perp_coin} price"
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
# ============================================================
|
|
336
|
-
# TEST 7: Market Close (BTC)
|
|
337
|
-
# ============================================================
|
|
338
|
-
separator('TEST 7: Market Close (BTC)')
|
|
339
|
-
|
|
340
|
-
if btc_price&.positive?
|
|
341
|
-
perp_size = (20.0 / btc_price).ceil(sz_decimals)
|
|
342
|
-
|
|
343
|
-
puts "#{perp_coin} mid: $#{btc_price.round(2)}"
|
|
344
|
-
puts "Size: #{perp_size} BTC"
|
|
345
|
-
puts
|
|
346
|
-
|
|
347
|
-
# Open a small long position
|
|
348
|
-
puts 'Opening LONG position (market buy)...'
|
|
349
|
-
result = sdk.exchange.market_order(
|
|
350
|
-
coin: perp_coin,
|
|
351
|
-
is_buy: true,
|
|
352
|
-
size: perp_size,
|
|
353
|
-
slippage: PERP_SLIPPAGE
|
|
354
|
-
)
|
|
355
|
-
check_result(result, 'Long open')
|
|
356
|
-
|
|
357
|
-
wait_with_countdown(WAIT_SECONDS, 'Position open. Waiting before market_close...')
|
|
358
|
-
|
|
359
|
-
# Close using market_close (auto-detect size)
|
|
360
|
-
puts 'Closing position using market_close (auto-detect size)...'
|
|
361
|
-
result = sdk.exchange.market_close(
|
|
362
|
-
coin: perp_coin,
|
|
363
|
-
slippage: PERP_SLIPPAGE
|
|
364
|
-
)
|
|
365
|
-
check_result(result, 'Market close')
|
|
366
|
-
else
|
|
367
|
-
puts "SKIPPED: Could not get #{perp_coin} price"
|
|
368
|
-
end
|
|
12
|
+
# Run a single test:
|
|
13
|
+
# HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_08_usd_class_transfer.rb
|
|
14
|
+
#
|
|
15
|
+
# See scripts/test_all.rb for the full list.
|
|
369
16
|
|
|
370
|
-
|
|
371
|
-
# Summary
|
|
372
|
-
# ============================================================
|
|
373
|
-
separator('INTEGRATION TEST COMPLETE')
|
|
374
|
-
puts 'All tests executed. Check your testnet wallet for trade history:'
|
|
375
|
-
puts 'https://app.hyperliquid-testnet.xyz'
|
|
376
|
-
puts
|
|
17
|
+
load File.join(__dir__, 'scripts', 'test_all.rb')
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hyperliquid
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- carter2099
|
|
@@ -99,6 +99,18 @@ files:
|
|
|
99
99
|
- lib/hyperliquid/signing/eip712.rb
|
|
100
100
|
- lib/hyperliquid/signing/signer.rb
|
|
101
101
|
- lib/hyperliquid/version.rb
|
|
102
|
+
- scripts/test_01_spot_market_roundtrip.rb
|
|
103
|
+
- scripts/test_02_spot_limit_order.rb
|
|
104
|
+
- scripts/test_03_perp_market_roundtrip.rb
|
|
105
|
+
- scripts/test_04_perp_limit_order.rb
|
|
106
|
+
- scripts/test_05_update_leverage.rb
|
|
107
|
+
- scripts/test_06_modify_order.rb
|
|
108
|
+
- scripts/test_07_market_close.rb
|
|
109
|
+
- scripts/test_08_usd_class_transfer.rb
|
|
110
|
+
- scripts/test_09_sub_account_lifecycle.rb
|
|
111
|
+
- scripts/test_10_vault_transfer.rb
|
|
112
|
+
- scripts/test_all.rb
|
|
113
|
+
- scripts/test_helpers.rb
|
|
102
114
|
- sig/hyperliquid.rbs
|
|
103
115
|
- test_integration.rb
|
|
104
116
|
homepage: https://github.com/carter2099/hyperliquid
|