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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +63 -0
  3. data/README.md +26 -0
  4. data/docs/API.md +374 -152
  5. data/docs/CONFIGURATION.md +25 -11
  6. data/docs/DEVELOPMENT.md +93 -15
  7. data/docs/ERRORS.md +2 -0
  8. data/docs/EXAMPLES.md +253 -2
  9. data/docs/WS.md +78 -20
  10. data/lib/hyperliquid/client.rb +19 -10
  11. data/lib/hyperliquid/exchange.rb +844 -22
  12. data/lib/hyperliquid/info.rb +23 -11
  13. data/lib/hyperliquid/signing/signer.rb +18 -1
  14. data/lib/hyperliquid/version.rb +1 -1
  15. data/lib/hyperliquid/ws/client.rb +192 -78
  16. data/lib/hyperliquid.rb +6 -0
  17. metadata +16 -34
  18. data/.rspec +0 -3
  19. data/.rubocop.yml +0 -49
  20. data/.ruby-version +0 -1
  21. data/CLAUDE.md +0 -104
  22. data/CODE_OF_CONDUCT.md +0 -132
  23. data/Rakefile +0 -12
  24. data/example.rb +0 -161
  25. data/scripts/test_01_spot_market_roundtrip.rb +0 -48
  26. data/scripts/test_02_spot_limit_order.rb +0 -48
  27. data/scripts/test_03_perp_market_roundtrip.rb +0 -58
  28. data/scripts/test_04_perp_limit_order.rb +0 -52
  29. data/scripts/test_05_update_leverage.rb +0 -50
  30. data/scripts/test_06_modify_order.rb +0 -67
  31. data/scripts/test_07_market_close.rb +0 -55
  32. data/scripts/test_08_usd_class_transfer.rb +0 -36
  33. data/scripts/test_09_sub_account_lifecycle.rb +0 -51
  34. data/scripts/test_10_vault.rb +0 -68
  35. data/scripts/test_11_builder_fee.rb +0 -81
  36. data/scripts/test_12_staking.rb +0 -70
  37. data/scripts/test_13_ws_l2_book.rb +0 -83
  38. data/scripts/test_14_ws_candle.rb +0 -81
  39. data/scripts/test_15_explorer.rb +0 -71
  40. data/scripts/test_16_send_to_evm_with_data.rb +0 -79
  41. data/scripts/test_17_create_vault.rb +0 -73
  42. data/scripts/test_18_user_portfolio_margin.rb +0 -72
  43. data/scripts/test_19_spot_user.rb +0 -55
  44. data/scripts/test_20_explorer_ws.rb +0 -92
  45. data/scripts/test_all.rb +0 -96
  46. data/scripts/test_automated.rb +0 -82
  47. data/scripts/test_helpers.rb +0 -238
  48. data/sig/hyperliquid.rbs +0 -4
  49. data/test_integration.rb +0 -17
@@ -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)
@@ -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
@@ -1,4 +0,0 @@
1
- module Hyperliquid
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
- end
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')