hyperliquid 1.9.0 → 2.0.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/CHANGELOG.md +63 -0
- data/README.md +26 -0
- data/docs/API.md +374 -152
- data/docs/CONFIGURATION.md +25 -11
- data/docs/DEVELOPMENT.md +93 -15
- data/docs/ERRORS.md +2 -0
- data/docs/EXAMPLES.md +253 -2
- data/docs/WS.md +78 -20
- data/lib/hyperliquid/client.rb +19 -10
- data/lib/hyperliquid/exchange.rb +844 -22
- data/lib/hyperliquid/info.rb +23 -11
- data/lib/hyperliquid/signing/signer.rb +18 -1
- data/lib/hyperliquid/version.rb +1 -1
- data/lib/hyperliquid/ws/client.rb +192 -78
- data/lib/hyperliquid.rb +6 -0
- metadata +16 -34
- data/.rspec +0 -3
- data/.rubocop.yml +0 -49
- data/.ruby-version +0 -1
- data/CLAUDE.md +0 -104
- data/CODE_OF_CONDUCT.md +0 -132
- data/Rakefile +0 -12
- data/example.rb +0 -161
- data/scripts/test_01_spot_market_roundtrip.rb +0 -48
- data/scripts/test_02_spot_limit_order.rb +0 -48
- data/scripts/test_03_perp_market_roundtrip.rb +0 -58
- data/scripts/test_04_perp_limit_order.rb +0 -52
- data/scripts/test_05_update_leverage.rb +0 -50
- data/scripts/test_06_modify_order.rb +0 -67
- data/scripts/test_07_market_close.rb +0 -55
- data/scripts/test_08_usd_class_transfer.rb +0 -36
- data/scripts/test_09_sub_account_lifecycle.rb +0 -51
- data/scripts/test_10_vault.rb +0 -68
- data/scripts/test_11_builder_fee.rb +0 -81
- data/scripts/test_12_staking.rb +0 -70
- data/scripts/test_13_ws_l2_book.rb +0 -83
- data/scripts/test_14_ws_candle.rb +0 -81
- data/scripts/test_15_explorer.rb +0 -71
- data/scripts/test_16_send_to_evm_with_data.rb +0 -79
- data/scripts/test_17_create_vault.rb +0 -73
- data/scripts/test_18_user_portfolio_margin.rb +0 -72
- data/scripts/test_19_spot_user.rb +0 -55
- data/scripts/test_20_explorer_ws.rb +0 -92
- data/scripts/test_all.rb +0 -96
- data/scripts/test_automated.rb +0 -82
- data/scripts/test_helpers.rb +0 -238
- data/sig/hyperliquid.rbs +0 -4
- data/test_integration.rb +0 -17
data/scripts/test_automated.rb
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Hyperliquid Ruby SDK - Automated Integration Test Runner
|
|
5
|
-
#
|
|
6
|
-
# Same as test_all.rb but excludes tests unsuitable for unattended runs:
|
|
7
|
-
# - test_09_sub_account_lifecycle.rb: requires $100k traded volume on testnet to create sub-accounts
|
|
8
|
-
# - test_12_staking.rb: requires HYPE token balance (locking issues)
|
|
9
|
-
#
|
|
10
|
-
# Usage:
|
|
11
|
-
# HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_automated.rb
|
|
12
|
-
|
|
13
|
-
SCRIPTS = [
|
|
14
|
-
'test_01_spot_market_roundtrip.rb',
|
|
15
|
-
'test_02_spot_limit_order.rb',
|
|
16
|
-
'test_03_perp_market_roundtrip.rb',
|
|
17
|
-
'test_04_perp_limit_order.rb',
|
|
18
|
-
'test_05_update_leverage.rb',
|
|
19
|
-
'test_06_modify_order.rb',
|
|
20
|
-
'test_07_market_close.rb',
|
|
21
|
-
'test_08_usd_class_transfer.rb',
|
|
22
|
-
'test_10_vault.rb',
|
|
23
|
-
'test_11_builder_fee.rb',
|
|
24
|
-
'test_13_ws_l2_book.rb',
|
|
25
|
-
'test_14_ws_candle.rb',
|
|
26
|
-
'test_15_explorer.rb',
|
|
27
|
-
'test_20_explorer_ws.rb'
|
|
28
|
-
].freeze
|
|
29
|
-
|
|
30
|
-
def green(text)
|
|
31
|
-
"\e[32m#{text}\e[0m"
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def red(text)
|
|
35
|
-
"\e[31m#{text}\e[0m"
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
unless ENV['HYPERLIQUID_PRIVATE_KEY']
|
|
39
|
-
puts red('Error: Set HYPERLIQUID_PRIVATE_KEY environment variable')
|
|
40
|
-
puts 'Usage: HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_automated.rb'
|
|
41
|
-
exit 1
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
scripts_dir = __dir__
|
|
45
|
-
passed = []
|
|
46
|
-
failed = []
|
|
47
|
-
|
|
48
|
-
SCRIPTS.each do |script|
|
|
49
|
-
path = File.join(scripts_dir, script)
|
|
50
|
-
puts
|
|
51
|
-
puts '#' * 60
|
|
52
|
-
puts "# Running: #{script}"
|
|
53
|
-
puts '#' * 60
|
|
54
|
-
|
|
55
|
-
success = system(RbConfig.ruby, path)
|
|
56
|
-
|
|
57
|
-
if success
|
|
58
|
-
passed << script
|
|
59
|
-
else
|
|
60
|
-
failed << script
|
|
61
|
-
puts red("!!! #{script} exited with error !!!")
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
puts
|
|
66
|
-
puts '=' * 60
|
|
67
|
-
puts 'INTEGRATION TEST SUMMARY'
|
|
68
|
-
puts '=' * 60
|
|
69
|
-
puts
|
|
70
|
-
puts "Passed: #{passed.length}/#{SCRIPTS.length}"
|
|
71
|
-
passed.each { |s| puts green(" [PASS] #{s}") }
|
|
72
|
-
if failed.any?
|
|
73
|
-
puts
|
|
74
|
-
puts "Failed: #{failed.length}/#{SCRIPTS.length}"
|
|
75
|
-
failed.each { |s| puts red(" [FAIL] #{s}") }
|
|
76
|
-
end
|
|
77
|
-
puts
|
|
78
|
-
puts 'Check your testnet wallet for trade history:'
|
|
79
|
-
puts 'https://app.hyperliquid-testnet.xyz'
|
|
80
|
-
puts
|
|
81
|
-
|
|
82
|
-
exit(failed.empty? ? 0 : 1)
|
data/scripts/test_helpers.rb
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative '../lib/hyperliquid'
|
|
4
|
-
require 'json'
|
|
5
|
-
|
|
6
|
-
WAIT_SECONDS = 3
|
|
7
|
-
SPOT_SLIPPAGE = 0.40 # 40% for illiquid testnet spot markets
|
|
8
|
-
PERP_SLIPPAGE = 0.15
|
|
9
|
-
ORACLE_RETRY_ATTEMPTS = 3
|
|
10
|
-
ORACLE_SLIPPAGE_INCREMENT = 0.10 # Increase slippage by 10% on each retry
|
|
11
|
-
|
|
12
|
-
def green(text)
|
|
13
|
-
"\e[32m#{text}\e[0m"
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def red(text)
|
|
17
|
-
"\e[31m#{text}\e[0m"
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
$test_failed = false
|
|
21
|
-
|
|
22
|
-
def separator(title)
|
|
23
|
-
puts
|
|
24
|
-
puts '=' * 60
|
|
25
|
-
puts title
|
|
26
|
-
puts '=' * 60
|
|
27
|
-
puts
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def wait_with_countdown(seconds, message)
|
|
31
|
-
puts message
|
|
32
|
-
seconds.downto(1) do |i|
|
|
33
|
-
print "\r #{i} seconds remaining... "
|
|
34
|
-
sleep 1
|
|
35
|
-
end
|
|
36
|
-
puts "\r Done! "
|
|
37
|
-
puts
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def api_error?(result)
|
|
41
|
-
return false unless result.is_a?(Hash) && result['status'] == 'err'
|
|
42
|
-
|
|
43
|
-
$test_failed = true
|
|
44
|
-
puts red("FAILED: #{result['response']}")
|
|
45
|
-
true
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Extract and display the status from an API response
|
|
49
|
-
def dump_status(result)
|
|
50
|
-
return unless result.is_a?(Hash)
|
|
51
|
-
|
|
52
|
-
response = result['response']
|
|
53
|
-
return unless response.is_a?(Hash)
|
|
54
|
-
|
|
55
|
-
data = response['data']
|
|
56
|
-
return unless data.is_a?(Hash)
|
|
57
|
-
|
|
58
|
-
status = data.dig('statuses', 0)
|
|
59
|
-
return unless status
|
|
60
|
-
|
|
61
|
-
puts " API status: #{status.inspect}"
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def check_result(result, operation)
|
|
65
|
-
dump_status(result)
|
|
66
|
-
|
|
67
|
-
return false if api_error?(result)
|
|
68
|
-
|
|
69
|
-
response = result['response']
|
|
70
|
-
data = response.is_a?(Hash) ? response['data'] : nil
|
|
71
|
-
status = data.is_a?(Hash) ? data.dig('statuses', 0) : nil
|
|
72
|
-
|
|
73
|
-
if status.is_a?(Hash) && status['error']
|
|
74
|
-
$test_failed = true
|
|
75
|
-
puts red("FAILED: #{status['error']}")
|
|
76
|
-
return false
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
if status.is_a?(Hash) && status['resting']
|
|
80
|
-
puts green("Order resting with OID: #{status['resting']['oid']}")
|
|
81
|
-
return status['resting']['oid']
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
if status == 'success' || (status.is_a?(Hash) && status['filled'])
|
|
85
|
-
puts green("#{operation} successful!")
|
|
86
|
-
return true
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
puts "Result: #{status.inspect}"
|
|
90
|
-
true
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def test_passed(name)
|
|
94
|
-
puts
|
|
95
|
-
if $test_failed
|
|
96
|
-
puts red("#{name} FAILED!")
|
|
97
|
-
exit 1
|
|
98
|
-
else
|
|
99
|
-
puts green("#{name} passed!")
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def build_sdk
|
|
104
|
-
private_key = ENV['HYPERLIQUID_PRIVATE_KEY']
|
|
105
|
-
unless private_key
|
|
106
|
-
puts red('Error: Set HYPERLIQUID_PRIVATE_KEY environment variable')
|
|
107
|
-
puts 'Usage: HYPERLIQUID_PRIVATE_KEY=0x... ruby <script>'
|
|
108
|
-
exit 1
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
sdk = Hyperliquid.new(
|
|
112
|
-
testnet: true,
|
|
113
|
-
private_key: private_key
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
puts "Wallet: #{sdk.exchange.address}"
|
|
117
|
-
puts 'Network: Testnet'
|
|
118
|
-
puts "Testnet UI: https://app.hyperliquid-testnet.xyz"
|
|
119
|
-
puts
|
|
120
|
-
|
|
121
|
-
sdk
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Check if result has "Price too far from oracle" error
|
|
125
|
-
def oracle_error?(result)
|
|
126
|
-
return false unless result.is_a?(Hash)
|
|
127
|
-
|
|
128
|
-
if result['status'] == 'err' && result['response'].to_s.include?('Price too far from oracle')
|
|
129
|
-
return true
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
status = result.dig('response', 'data', 'statuses', 0)
|
|
133
|
-
status.is_a?(Hash) && status['error'].to_s.include?('Price too far from oracle')
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Execute a market order with retry logic for oracle errors
|
|
137
|
-
def market_order_with_retry(sdk, coin:, is_buy:, size:, base_slippage:)
|
|
138
|
-
slippage = base_slippage
|
|
139
|
-
|
|
140
|
-
ORACLE_RETRY_ATTEMPTS.times do |attempt|
|
|
141
|
-
result = sdk.exchange.market_order(
|
|
142
|
-
coin: coin,
|
|
143
|
-
is_buy: is_buy,
|
|
144
|
-
size: size,
|
|
145
|
-
slippage: slippage
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
unless oracle_error?(result)
|
|
149
|
-
return result
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
if attempt < ORACLE_RETRY_ATTEMPTS - 1
|
|
153
|
-
slippage += ORACLE_SLIPPAGE_INCREMENT
|
|
154
|
-
puts red("Oracle price error. Retrying with #{(slippage * 100).to_i}% slippage (attempt #{attempt + 2}/#{ORACLE_RETRY_ATTEMPTS})...")
|
|
155
|
-
sleep 1
|
|
156
|
-
else
|
|
157
|
-
return result
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
# Get position for a coin, returns nil if no position
|
|
163
|
-
def get_position(sdk, coin)
|
|
164
|
-
state = sdk.info.user_state(sdk.exchange.address)
|
|
165
|
-
positions = state['assetPositions'] || []
|
|
166
|
-
positions.find { |p| p.dig('position', 'coin') == coin }
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
# Check for open position and prompt user to clean up
|
|
170
|
-
# Returns true if test should continue, false if should skip
|
|
171
|
-
def check_position_and_prompt(sdk, coin, timeout: 10)
|
|
172
|
-
position = get_position(sdk, coin)
|
|
173
|
-
return true unless position
|
|
174
|
-
|
|
175
|
-
size = position.dig('position', 'szi').to_f
|
|
176
|
-
return true if size.zero?
|
|
177
|
-
|
|
178
|
-
puts red("WARNING: Open #{coin} position detected (size: #{size})")
|
|
179
|
-
puts "This test requires no open #{coin} position."
|
|
180
|
-
puts
|
|
181
|
-
puts "Options:"
|
|
182
|
-
puts " [c] Close the position and continue"
|
|
183
|
-
puts " [s] Skip this test"
|
|
184
|
-
puts
|
|
185
|
-
print "Choice (auto-skip in #{timeout}s): "
|
|
186
|
-
$stdout.flush
|
|
187
|
-
|
|
188
|
-
# Non-blocking read with timeout
|
|
189
|
-
require 'io/wait'
|
|
190
|
-
choice = nil
|
|
191
|
-
start = Time.now
|
|
192
|
-
loop do
|
|
193
|
-
if $stdin.ready?
|
|
194
|
-
choice = $stdin.gets&.strip&.downcase
|
|
195
|
-
break
|
|
196
|
-
end
|
|
197
|
-
elapsed = Time.now - start
|
|
198
|
-
remaining = (timeout - elapsed).ceil
|
|
199
|
-
if elapsed >= timeout
|
|
200
|
-
puts
|
|
201
|
-
puts "No response, skipping test..."
|
|
202
|
-
return false
|
|
203
|
-
end
|
|
204
|
-
print "\rChoice (auto-skip in #{remaining}s): "
|
|
205
|
-
$stdout.flush
|
|
206
|
-
sleep 0.5
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
case choice
|
|
210
|
-
when 'c'
|
|
211
|
-
puts "Closing #{coin} position..."
|
|
212
|
-
result = sdk.exchange.market_close(coin: coin, slippage: PERP_SLIPPAGE + 0.20)
|
|
213
|
-
dump_status(result)
|
|
214
|
-
if api_error?(result)
|
|
215
|
-
puts red("Failed to close position. Skipping test.")
|
|
216
|
-
return false
|
|
217
|
-
end
|
|
218
|
-
puts green("Position closed. Waiting for settlement...")
|
|
219
|
-
|
|
220
|
-
# Wait and verify position is actually closed
|
|
221
|
-
5.times do |i|
|
|
222
|
-
sleep 2
|
|
223
|
-
position = get_position(sdk, coin)
|
|
224
|
-
remaining = position&.dig('position', 'szi').to_f.abs
|
|
225
|
-
if remaining < 0.000001
|
|
226
|
-
puts green("Position fully settled.")
|
|
227
|
-
return true
|
|
228
|
-
end
|
|
229
|
-
puts " Still settling... (#{remaining} remaining)"
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
puts red("Position did not fully close. Skipping test.")
|
|
233
|
-
false
|
|
234
|
-
else
|
|
235
|
-
puts "Skipping test."
|
|
236
|
-
false
|
|
237
|
-
end
|
|
238
|
-
end
|
data/sig/hyperliquid.rbs
DELETED
data/test_integration.rb
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Hyperliquid Ruby SDK - Testnet Integration Tests
|
|
5
|
-
#
|
|
6
|
-
# This is a convenience wrapper that runs all integration tests.
|
|
7
|
-
# Individual tests live in scripts/ and can be run standalone.
|
|
8
|
-
#
|
|
9
|
-
# Usage:
|
|
10
|
-
# HYPERLIQUID_PRIVATE_KEY=0x... ruby test_integration.rb
|
|
11
|
-
#
|
|
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.
|
|
16
|
-
|
|
17
|
-
load File.join(__dir__, 'scripts', 'test_all.rb')
|