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
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Test 14: WebSocket candle Subscription
|
|
5
|
-
#
|
|
6
|
-
# Subscribes to ETH 1m candles on testnet, prints 3 updates
|
|
7
|
-
# with OHLCV data, then cleanly disconnects.
|
|
8
|
-
#
|
|
9
|
-
# No private key required (read-only WebSocket).
|
|
10
|
-
#
|
|
11
|
-
# Usage:
|
|
12
|
-
# ruby scripts/test_14_ws_candle.rb
|
|
13
|
-
|
|
14
|
-
require_relative '../lib/hyperliquid'
|
|
15
|
-
|
|
16
|
-
def green(text)
|
|
17
|
-
"\e[32m#{text}\e[0m"
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def red(text)
|
|
21
|
-
"\e[31m#{text}\e[0m"
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
puts
|
|
25
|
-
puts '=' * 60
|
|
26
|
-
puts 'TEST 14: WebSocket candle Subscription'
|
|
27
|
-
puts '=' * 60
|
|
28
|
-
puts
|
|
29
|
-
puts 'Network: Testnet'
|
|
30
|
-
puts 'Subscribing to ETH 1m candles (3 updates, then disconnect)'
|
|
31
|
-
puts
|
|
32
|
-
|
|
33
|
-
sdk = Hyperliquid.new(testnet: true)
|
|
34
|
-
updates = []
|
|
35
|
-
mutex = Mutex.new
|
|
36
|
-
done = ConditionVariable.new
|
|
37
|
-
|
|
38
|
-
sdk.ws.on(:open) { puts 'WebSocket connected.' }
|
|
39
|
-
sdk.ws.on(:error) { |e| puts red("WebSocket error: #{e}") }
|
|
40
|
-
|
|
41
|
-
sdk.ws.subscribe({ type: 'candle', coin: 'ETH', interval: '1m' }) do |data|
|
|
42
|
-
mutex.synchronize do
|
|
43
|
-
next if updates.length >= 3
|
|
44
|
-
|
|
45
|
-
update_num = updates.length + 1
|
|
46
|
-
puts "Update #{update_num}/3:"
|
|
47
|
-
puts " Symbol: #{data['s']}"
|
|
48
|
-
puts " Interval: #{data['i']}"
|
|
49
|
-
puts " Open: #{data['o']}"
|
|
50
|
-
puts " High: #{data['h']}"
|
|
51
|
-
puts " Low: #{data['l']}"
|
|
52
|
-
puts " Close: #{data['c']}"
|
|
53
|
-
puts " Volume: #{data['v']}"
|
|
54
|
-
puts
|
|
55
|
-
|
|
56
|
-
updates << data
|
|
57
|
-
done.signal if updates.length >= 3
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Wait for 3 updates or timeout after 90 seconds (candles can be slow)
|
|
62
|
-
success = false
|
|
63
|
-
mutex.synchronize do
|
|
64
|
-
deadline = Time.now + 90
|
|
65
|
-
while updates.length < 3 && Time.now < deadline
|
|
66
|
-
remaining = deadline - Time.now
|
|
67
|
-
break if remaining <= 0
|
|
68
|
-
|
|
69
|
-
done.wait(mutex, remaining)
|
|
70
|
-
end
|
|
71
|
-
success = updates.length >= 3
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
sdk.ws.close
|
|
75
|
-
|
|
76
|
-
if success
|
|
77
|
-
puts green('Test 14 WebSocket candle passed!')
|
|
78
|
-
else
|
|
79
|
-
puts red("Test 14 FAILED: only received #{updates.length}/3 updates within 90s")
|
|
80
|
-
exit 1
|
|
81
|
-
end
|
data/scripts/test_15_explorer.rb
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Test 15: Explorer RPC (tx_details + user_details)
|
|
5
|
-
#
|
|
6
|
-
# Calls the explorer RPC for the wallet's recent transactions:
|
|
7
|
-
# 1. user_details(wallet_address) -> list of recent txs
|
|
8
|
-
# 2. tx_details(first tx hash) -> full details of one tx
|
|
9
|
-
#
|
|
10
|
-
# Requires HYPERLIQUID_PRIVATE_KEY (used to derive wallet address; no signing).
|
|
11
|
-
# A wallet with no testnet activity at all will produce a soft warning rather
|
|
12
|
-
# than fail — explorer testnet endpoint occasionally has its own state quirks.
|
|
13
|
-
#
|
|
14
|
-
# Usage:
|
|
15
|
-
# HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_15_explorer.rb
|
|
16
|
-
|
|
17
|
-
require_relative 'test_helpers'
|
|
18
|
-
|
|
19
|
-
separator('TEST 15: Explorer RPC (tx_details + user_details)')
|
|
20
|
-
|
|
21
|
-
sdk = build_sdk
|
|
22
|
-
|
|
23
|
-
begin
|
|
24
|
-
user_details_resp = sdk.info.user_details(sdk.exchange.address)
|
|
25
|
-
rescue Hyperliquid::Error => e
|
|
26
|
-
puts red("user_details FAILED: #{e.class}: #{e.message}")
|
|
27
|
-
exit 1
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
unless user_details_resp.is_a?(Hash) && user_details_resp['type'] == 'userDetails'
|
|
31
|
-
puts red("user_details: unexpected response shape: #{user_details_resp.inspect}")
|
|
32
|
-
exit 1
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
txs = user_details_resp['txs'] || []
|
|
36
|
-
puts green("user_details OK: #{txs.length} txs returned")
|
|
37
|
-
|
|
38
|
-
if txs.empty?
|
|
39
|
-
puts 'No txs on this wallet — skipping tx_details lookup.'
|
|
40
|
-
test_passed('Test 15 explorer RPC')
|
|
41
|
-
exit 0
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
first_hash = txs.first['hash']
|
|
45
|
-
unless first_hash.is_a?(String) && first_hash.start_with?('0x')
|
|
46
|
-
puts red("First tx hash missing or malformed: #{first_hash.inspect}")
|
|
47
|
-
exit 1
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
puts "Looking up tx_details for #{first_hash}..."
|
|
51
|
-
begin
|
|
52
|
-
tx_resp = sdk.info.tx_details(first_hash)
|
|
53
|
-
rescue Hyperliquid::Error => e
|
|
54
|
-
puts red("tx_details FAILED: #{e.class}: #{e.message}")
|
|
55
|
-
exit 1
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
unless tx_resp.is_a?(Hash) && tx_resp['type'] == 'txDetails'
|
|
59
|
-
puts red("tx_details: unexpected response shape: #{tx_resp.inspect}")
|
|
60
|
-
exit 1
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
tx = tx_resp['tx']
|
|
64
|
-
unless tx.is_a?(Hash) && tx['hash'] == first_hash
|
|
65
|
-
puts red("tx_details: hash mismatch or missing tx: #{tx.inspect}")
|
|
66
|
-
exit 1
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
puts green("tx_details OK: action=#{tx.dig('action', 'type')} time=#{tx['time']}")
|
|
70
|
-
|
|
71
|
-
test_passed('Test 15 explorer RPC')
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Test 16: sendToEvmWithData (user-signed exchange action)
|
|
5
|
-
#
|
|
6
|
-
# Mirrors the TS SDK's `tests/api/exchange/sendToEvmWithData.test.ts`:
|
|
7
|
-
# 1. Top up spot USDC by 2 (via usd_class_transfer perp -> spot).
|
|
8
|
-
# 2. Submit one real sendToEvmWithData action sending 1 USDC to 0x...01
|
|
9
|
-
# on HyperEVM testnet (chain 998) with empty calldata.
|
|
10
|
-
# 3. Assert response is { status: 'ok', response: { type: 'default' } }.
|
|
11
|
-
#
|
|
12
|
-
# This funds-moving test sends 1 testnet USDC to 0x...01 (effectively burned —
|
|
13
|
-
# no contract is there to redirect it). Same bar the TS SDK uses for its own
|
|
14
|
-
# test suite.
|
|
15
|
-
#
|
|
16
|
-
# Skips with a warning if the wallet is on unified account (usd_class_transfer
|
|
17
|
-
# is disabled in that mode and the top-up cannot run).
|
|
18
|
-
#
|
|
19
|
-
# Usage:
|
|
20
|
-
# HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_16_send_to_evm_with_data.rb
|
|
21
|
-
|
|
22
|
-
require_relative 'test_helpers'
|
|
23
|
-
|
|
24
|
-
sdk = build_sdk
|
|
25
|
-
separator('TEST 16: sendToEvmWithData')
|
|
26
|
-
|
|
27
|
-
abstraction = sdk.info.user_abstraction(sdk.exchange.address)
|
|
28
|
-
if abstraction == 'unifiedAccount'
|
|
29
|
-
puts "SKIPPED: Wallet has unified account active (abstraction=#{abstraction.inspect})."
|
|
30
|
-
puts ' usd_class_transfer top-up step is disabled by the exchange when balances are unified.'
|
|
31
|
-
test_passed('Test 16 sendToEvmWithData')
|
|
32
|
-
exit 0
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
puts 'Topping up spot USDC by $2 (perp -> spot)...'
|
|
36
|
-
top_up = sdk.exchange.usd_class_transfer(amount: '2', to_perp: false)
|
|
37
|
-
dump_status(top_up)
|
|
38
|
-
if api_error?(top_up)
|
|
39
|
-
puts red('Top-up failed; aborting before sendToEvmWithData.')
|
|
40
|
-
test_passed('Test 16 sendToEvmWithData')
|
|
41
|
-
exit 1
|
|
42
|
-
end
|
|
43
|
-
puts green('Top-up successful.')
|
|
44
|
-
puts
|
|
45
|
-
|
|
46
|
-
wait_with_countdown(WAIT_SECONDS, 'Waiting before sendToEvmWithData...')
|
|
47
|
-
|
|
48
|
-
puts 'Sending 1 USDC to 0x...01 on HyperEVM testnet (chain 998) with empty calldata...'
|
|
49
|
-
result = sdk.exchange.send_to_evm_with_data(
|
|
50
|
-
token: 'USDC',
|
|
51
|
-
amount: '1',
|
|
52
|
-
source_dex: 'spot',
|
|
53
|
-
destination_recipient: '0x0000000000000000000000000000000000000001',
|
|
54
|
-
address_encoding: 'hex',
|
|
55
|
-
destination_chain_id: 998,
|
|
56
|
-
gas_limit: 200_000,
|
|
57
|
-
data: '0x'
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
if api_error?(result)
|
|
61
|
-
puts red("sendToEvmWithData FAILED: #{result.inspect}")
|
|
62
|
-
test_passed('Test 16 sendToEvmWithData')
|
|
63
|
-
exit 1
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
unless result.is_a?(Hash) && result['status'] == 'ok'
|
|
67
|
-
$test_failed = true
|
|
68
|
-
puts red("Unexpected status: #{result.inspect}")
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
response_type = result.dig('response', 'type')
|
|
72
|
-
unless response_type == 'default'
|
|
73
|
-
$test_failed = true
|
|
74
|
-
puts red("Expected response.type 'default', got: #{response_type.inspect}")
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
puts green("sendToEvmWithData OK: status=ok, response.type=#{response_type}") unless $test_failed
|
|
78
|
-
|
|
79
|
-
test_passed('Test 16 sendToEvmWithData')
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Test 17: createVault (L1 exchange action)
|
|
5
|
-
#
|
|
6
|
-
# Creates a new vault with the calling wallet as leader, using a $100 testnet
|
|
7
|
-
# USDC seed (server-enforced minimum). On success, prints the new vault address
|
|
8
|
-
# from response.data.
|
|
9
|
-
#
|
|
10
|
-
# WARNING: This is a real testnet action. The $100 seed is locked into the
|
|
11
|
-
# created vault per Hyperliquid's vault lockup rules. Run intentionally —
|
|
12
|
-
# do NOT wire into test_automated.rb.
|
|
13
|
-
#
|
|
14
|
-
# Skips with a warning if the wallet has < $100 perp USDC available.
|
|
15
|
-
#
|
|
16
|
-
# Usage:
|
|
17
|
-
# HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_17_create_vault.rb
|
|
18
|
-
|
|
19
|
-
require_relative 'test_helpers'
|
|
20
|
-
|
|
21
|
-
sdk = build_sdk
|
|
22
|
-
separator('TEST 17: createVault')
|
|
23
|
-
|
|
24
|
-
state = sdk.info.user_state(sdk.exchange.address)
|
|
25
|
-
withdrawable = state['withdrawable'].to_f
|
|
26
|
-
puts "Wallet: #{sdk.exchange.address}"
|
|
27
|
-
puts "Withdrawable: $#{format('%.2f', withdrawable)}"
|
|
28
|
-
puts
|
|
29
|
-
|
|
30
|
-
if withdrawable < 100
|
|
31
|
-
puts red("SKIPPED: Need >= $100 perp USDC to seed a vault (have $#{format('%.2f', withdrawable)}).")
|
|
32
|
-
test_passed('Test 17 createVault')
|
|
33
|
-
exit 0
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
vault_name = "AgentVault#{Time.now.to_i}"
|
|
37
|
-
vault_description = 'Vault created by hyperliquid-run integration test (test_17).'
|
|
38
|
-
|
|
39
|
-
puts "Creating vault \"#{vault_name}\" with $100 seed..."
|
|
40
|
-
result = sdk.exchange.create_vault(
|
|
41
|
-
name: vault_name,
|
|
42
|
-
description: vault_description,
|
|
43
|
-
initial_usd: 100
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
if api_error?(result)
|
|
47
|
-
puts red("createVault FAILED: #{result.inspect}")
|
|
48
|
-
test_passed('Test 17 createVault')
|
|
49
|
-
exit 1
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
unless result.is_a?(Hash) && result['status'] == 'ok'
|
|
53
|
-
$test_failed = true
|
|
54
|
-
puts red("Unexpected status: #{result.inspect}")
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
response_type = result.dig('response', 'type')
|
|
58
|
-
unless response_type == 'createVault'
|
|
59
|
-
$test_failed = true
|
|
60
|
-
puts red("Expected response.type 'createVault', got: #{response_type.inspect}")
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
vault_address = result.dig('response', 'data')
|
|
64
|
-
unless vault_address.is_a?(String) && vault_address.start_with?('0x') && vault_address.length == 42
|
|
65
|
-
$test_failed = true
|
|
66
|
-
puts red("Expected response.data to be a 0x-prefixed 40-hex-char vault address, got: #{vault_address.inspect}")
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
unless $test_failed
|
|
70
|
-
puts green("createVault OK: status=ok, response.type=#{response_type}, vault=#{vault_address}")
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
test_passed('Test 17 createVault')
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Test 18: userPortfolioMargin (user-signed exchange action)
|
|
5
|
-
#
|
|
6
|
-
# Toggles cross-portfolio-margin mode for the calling wallet. Sends two actions:
|
|
7
|
-
# enable, then disable, leaving the wallet in its original state.
|
|
8
|
-
#
|
|
9
|
-
# WARNING: Toggling portfolio margin alters margining math on existing perp positions.
|
|
10
|
-
# Run only on a wallet without significant open exposure on testnet.
|
|
11
|
-
#
|
|
12
|
-
# Usage:
|
|
13
|
-
# HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_18_user_portfolio_margin.rb
|
|
14
|
-
|
|
15
|
-
require_relative 'test_helpers'
|
|
16
|
-
|
|
17
|
-
sdk = build_sdk
|
|
18
|
-
separator('TEST 18: userPortfolioMargin')
|
|
19
|
-
|
|
20
|
-
user = sdk.exchange.address
|
|
21
|
-
puts "Wallet: #{user}"
|
|
22
|
-
puts
|
|
23
|
-
|
|
24
|
-
puts 'Enabling portfolio margin...'
|
|
25
|
-
result = sdk.exchange.user_portfolio_margin(user: user, enabled: true)
|
|
26
|
-
|
|
27
|
-
# The protocol enforces a $10k account value or $5m volume threshold for portfolio
|
|
28
|
-
# margin eligibility. The agent testnet wallet typically meets neither, so this
|
|
29
|
-
# precondition failure is not an SDK bug — downgrade to warning and exit cleanly,
|
|
30
|
-
# matching the test_08 / test_11 pattern.
|
|
31
|
-
if result.is_a?(Hash) && result['status'] == 'err' &&
|
|
32
|
-
result['response'].to_s.include?('Portfolio margin requires')
|
|
33
|
-
puts red("WARNING: #{result['response']}")
|
|
34
|
-
puts ' Skipping — this is a testnet precondition, not an SDK failure.'
|
|
35
|
-
puts ' The action correctly serialized and signed (server returned a structured'
|
|
36
|
-
puts ' rejection, not a signing error).'
|
|
37
|
-
test_passed('Test 18 user_portfolio_margin')
|
|
38
|
-
exit 0
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
if api_error?(result)
|
|
42
|
-
puts red("user_portfolio_margin (enable) FAILED: #{result.inspect}")
|
|
43
|
-
test_passed('Test 18 user_portfolio_margin')
|
|
44
|
-
exit 1
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
unless result.is_a?(Hash) && result['status'] == 'ok' && result.dig('response', 'type') == 'default'
|
|
48
|
-
$test_failed = true
|
|
49
|
-
puts red("Unexpected enable response: #{result.inspect}")
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
puts green("Enable OK: #{result.inspect}") unless $test_failed
|
|
53
|
-
|
|
54
|
-
wait_with_countdown(WAIT_SECONDS, 'Settling before toggle back...')
|
|
55
|
-
|
|
56
|
-
puts 'Disabling portfolio margin...'
|
|
57
|
-
result = sdk.exchange.user_portfolio_margin(user: user, enabled: false)
|
|
58
|
-
|
|
59
|
-
if api_error?(result)
|
|
60
|
-
puts red("user_portfolio_margin (disable) FAILED: #{result.inspect}")
|
|
61
|
-
test_passed('Test 18 user_portfolio_margin')
|
|
62
|
-
exit 1
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
unless result.is_a?(Hash) && result['status'] == 'ok' && result.dig('response', 'type') == 'default'
|
|
66
|
-
$test_failed = true
|
|
67
|
-
puts red("Unexpected disable response: #{result.inspect}")
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
puts green("Disable OK: #{result.inspect}") unless $test_failed
|
|
71
|
-
|
|
72
|
-
test_passed('Test 18 user_portfolio_margin')
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Test 19: spotUser (L1 exchange action)
|
|
5
|
-
#
|
|
6
|
-
# Toggles spot-dusting opt-out for the calling wallet. Sends two actions:
|
|
7
|
-
# opt_out: true, then opt_out: false, leaving the wallet in its original state
|
|
8
|
-
# (spot dusting opted-in by default).
|
|
9
|
-
#
|
|
10
|
-
# Usage:
|
|
11
|
-
# HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_19_spot_user.rb
|
|
12
|
-
|
|
13
|
-
require_relative 'test_helpers'
|
|
14
|
-
|
|
15
|
-
sdk = build_sdk
|
|
16
|
-
separator('TEST 19: spotUser (spot-dusting opt-out)')
|
|
17
|
-
|
|
18
|
-
puts "Wallet: #{sdk.exchange.address}"
|
|
19
|
-
puts
|
|
20
|
-
|
|
21
|
-
puts 'Opting out of spot dusting...'
|
|
22
|
-
result = sdk.exchange.spot_user(opt_out: true)
|
|
23
|
-
|
|
24
|
-
if api_error?(result)
|
|
25
|
-
puts red("spot_user (opt_out: true) FAILED: #{result.inspect}")
|
|
26
|
-
test_passed('Test 19 spot_user')
|
|
27
|
-
exit 1
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
unless result.is_a?(Hash) && result['status'] == 'ok' && result.dig('response', 'type') == 'default'
|
|
31
|
-
$test_failed = true
|
|
32
|
-
puts red("Unexpected opt-out response: #{result.inspect}")
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
puts green("Opt-out OK: #{result.inspect}") unless $test_failed
|
|
36
|
-
|
|
37
|
-
wait_with_countdown(WAIT_SECONDS, 'Settling before toggle back...')
|
|
38
|
-
|
|
39
|
-
puts 'Opting back in to spot dusting...'
|
|
40
|
-
result = sdk.exchange.spot_user(opt_out: false)
|
|
41
|
-
|
|
42
|
-
if api_error?(result)
|
|
43
|
-
puts red("spot_user (opt_out: false) FAILED: #{result.inspect}")
|
|
44
|
-
test_passed('Test 19 spot_user')
|
|
45
|
-
exit 1
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
unless result.is_a?(Hash) && result['status'] == 'ok' && result.dig('response', 'type') == 'default'
|
|
49
|
-
$test_failed = true
|
|
50
|
-
puts red("Unexpected opt-in response: #{result.inspect}")
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
puts green("Opt-in OK: #{result.inspect}") unless $test_failed
|
|
54
|
-
|
|
55
|
-
test_passed('Test 19 spot_user')
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Integration test for Explorer WebSocket subscription
|
|
5
|
-
# Connects to testnet explorer WS, subscribes to explorerBlock, and collects 3 block events
|
|
6
|
-
#
|
|
7
|
-
# Usage: ruby test_20_explorer_ws.rb
|
|
8
|
-
|
|
9
|
-
require_relative 'test_helpers'
|
|
10
|
-
|
|
11
|
-
puts '=' * 70
|
|
12
|
-
puts 'test_20: Explorer WebSocket subscription'
|
|
13
|
-
puts '=' * 70
|
|
14
|
-
|
|
15
|
-
# Initialize SDK with testnet
|
|
16
|
-
sdk = Hyperliquid::SDK.new(testnet: true)
|
|
17
|
-
|
|
18
|
-
puts "\n1. Testing explorer WebSocket connection and subscription..."
|
|
19
|
-
|
|
20
|
-
blocks_received = []
|
|
21
|
-
max_blocks = 3
|
|
22
|
-
timeout_seconds = 60
|
|
23
|
-
start_time = Time.now
|
|
24
|
-
|
|
25
|
-
puts " Subscribing to explorerBlock channel (expecting #{max_blocks} blocks within #{timeout_seconds}s)..."
|
|
26
|
-
puts " Explorer WS URL: wss://rpc.hyperliquid-testnet.xyz/ws"
|
|
27
|
-
|
|
28
|
-
# Subscribe to explorer blocks
|
|
29
|
-
sub_id = sdk.ws.subscribe_explorer_block do |block_array|
|
|
30
|
-
# Explorer messages arrive as arrays
|
|
31
|
-
block_array.each do |block|
|
|
32
|
-
blocks_received << block
|
|
33
|
-
elapsed = Time.now - start_time
|
|
34
|
-
puts " Block ##{blocks_received.size}: height=#{block['height']} hash=#{block['hash']} " \
|
|
35
|
-
"numTxs=#{block['numTxs']} proposer=#{block['proposer'][0..10]}... (#{elapsed.round(1)}s elapsed)"
|
|
36
|
-
|
|
37
|
-
break if blocks_received.size >= max_blocks
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
puts " Subscription ID: #{sub_id}"
|
|
42
|
-
|
|
43
|
-
# Wait for blocks with timeout
|
|
44
|
-
puts "\n2. Waiting for block events..."
|
|
45
|
-
loop do
|
|
46
|
-
break if blocks_received.size >= max_blocks
|
|
47
|
-
|
|
48
|
-
elapsed = Time.now - start_time
|
|
49
|
-
if elapsed > timeout_seconds
|
|
50
|
-
puts "\n ⚠ Timeout: Only received #{blocks_received.size}/#{max_blocks} blocks after #{timeout_seconds}s"
|
|
51
|
-
puts ' This may indicate testnet explorer is not producing blocks or WS connection issues'
|
|
52
|
-
break
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
sleep 0.5
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Cleanup
|
|
59
|
-
puts "\n3. Cleanup..."
|
|
60
|
-
sdk.ws.unsubscribe(sub_id)
|
|
61
|
-
puts " Unsubscribed from #{sub_id}"
|
|
62
|
-
|
|
63
|
-
sdk.ws.close
|
|
64
|
-
puts ' WebSocket connection closed'
|
|
65
|
-
|
|
66
|
-
# Verify results
|
|
67
|
-
puts "\n4. Verification..."
|
|
68
|
-
if blocks_received.empty?
|
|
69
|
-
puts ' ❌ FAIL: No blocks received'
|
|
70
|
-
puts ' Explorer WebSocket may not be working on testnet, or connection failed'
|
|
71
|
-
exit 1
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
puts " ✓ Received #{blocks_received.size} block(s)"
|
|
75
|
-
|
|
76
|
-
# Verify block structure
|
|
77
|
-
blocks_received.each_with_index do |block, idx|
|
|
78
|
-
required_fields = %w[height hash numTxs proposer blockTime]
|
|
79
|
-
missing = required_fields - block.keys
|
|
80
|
-
unless missing.empty?
|
|
81
|
-
puts " ⚠ Block ##{idx + 1} missing fields: #{missing.join(', ')}"
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
if blocks_received.size >= max_blocks
|
|
86
|
-
puts " ✓ PASS: Successfully received #{max_blocks} explorer blocks"
|
|
87
|
-
exit 0
|
|
88
|
-
else
|
|
89
|
-
puts " ⚠ PARTIAL: Received #{blocks_received.size}/#{max_blocks} blocks"
|
|
90
|
-
puts ' This is acceptable if testnet explorer is slow, but should be investigated'
|
|
91
|
-
exit 0 # Don't fail the test suite for partial results
|
|
92
|
-
end
|
data/scripts/test_all.rb
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Hyperliquid Ruby SDK - Testnet Integration Tests (Runner)
|
|
5
|
-
#
|
|
6
|
-
# Runs all integration test scripts in order.
|
|
7
|
-
# Each script can also be run individually for debugging.
|
|
8
|
-
#
|
|
9
|
-
# Prerequisites:
|
|
10
|
-
# - Testnet wallet with USDC balance
|
|
11
|
-
# - Get testnet funds from: https://app.hyperliquid-testnet.xyz
|
|
12
|
-
#
|
|
13
|
-
# Usage:
|
|
14
|
-
# HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_all.rb
|
|
15
|
-
#
|
|
16
|
-
# Run a single test:
|
|
17
|
-
# HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_08_usd_class_transfer.rb
|
|
18
|
-
#
|
|
19
|
-
# Note: These scripts execute real trades on testnet. No real funds are at risk.
|
|
20
|
-
|
|
21
|
-
SCRIPTS = [
|
|
22
|
-
'test_01_spot_market_roundtrip.rb',
|
|
23
|
-
'test_02_spot_limit_order.rb',
|
|
24
|
-
'test_03_perp_market_roundtrip.rb',
|
|
25
|
-
'test_04_perp_limit_order.rb',
|
|
26
|
-
'test_05_update_leverage.rb',
|
|
27
|
-
'test_06_modify_order.rb',
|
|
28
|
-
'test_07_market_close.rb',
|
|
29
|
-
'test_08_usd_class_transfer.rb',
|
|
30
|
-
'test_09_sub_account_lifecycle.rb',
|
|
31
|
-
'test_10_vault.rb',
|
|
32
|
-
'test_11_builder_fee.rb',
|
|
33
|
-
'test_12_staking.rb',
|
|
34
|
-
'test_13_ws_l2_book.rb',
|
|
35
|
-
'test_14_ws_candle.rb',
|
|
36
|
-
'test_15_explorer.rb',
|
|
37
|
-
'test_16_send_to_evm_with_data.rb',
|
|
38
|
-
'test_17_create_vault.rb',
|
|
39
|
-
'test_18_user_portfolio_margin.rb',
|
|
40
|
-
'test_19_spot_user.rb',
|
|
41
|
-
'test_20_explorer_ws.rb'
|
|
42
|
-
].freeze
|
|
43
|
-
|
|
44
|
-
def green(text)
|
|
45
|
-
"\e[32m#{text}\e[0m"
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def red(text)
|
|
49
|
-
"\e[31m#{text}\e[0m"
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
unless ENV['HYPERLIQUID_PRIVATE_KEY']
|
|
53
|
-
puts red('Error: Set HYPERLIQUID_PRIVATE_KEY environment variable')
|
|
54
|
-
puts 'Usage: HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_all.rb'
|
|
55
|
-
exit 1
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
scripts_dir = __dir__
|
|
59
|
-
passed = []
|
|
60
|
-
failed = []
|
|
61
|
-
|
|
62
|
-
SCRIPTS.each do |script|
|
|
63
|
-
path = File.join(scripts_dir, script)
|
|
64
|
-
puts
|
|
65
|
-
puts '#' * 60
|
|
66
|
-
puts "# Running: #{script}"
|
|
67
|
-
puts '#' * 60
|
|
68
|
-
|
|
69
|
-
success = system(RbConfig.ruby, path)
|
|
70
|
-
|
|
71
|
-
if success
|
|
72
|
-
passed << script
|
|
73
|
-
else
|
|
74
|
-
failed << script
|
|
75
|
-
puts red("!!! #{script} exited with error !!!")
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
puts
|
|
80
|
-
puts '=' * 60
|
|
81
|
-
puts 'INTEGRATION TEST SUMMARY'
|
|
82
|
-
puts '=' * 60
|
|
83
|
-
puts
|
|
84
|
-
puts "Passed: #{passed.length}/#{SCRIPTS.length}"
|
|
85
|
-
passed.each { |s| puts green(" [PASS] #{s}") }
|
|
86
|
-
if failed.any?
|
|
87
|
-
puts
|
|
88
|
-
puts "Failed: #{failed.length}/#{SCRIPTS.length}"
|
|
89
|
-
failed.each { |s| puts red(" [FAIL] #{s}") }
|
|
90
|
-
end
|
|
91
|
-
puts
|
|
92
|
-
puts 'Check your testnet wallet for trade history:'
|
|
93
|
-
puts 'https://app.hyperliquid-testnet.xyz'
|
|
94
|
-
puts
|
|
95
|
-
|
|
96
|
-
exit(failed.empty? ? 0 : 1)
|