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,58 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Test 3: Perp Market Roundtrip (Long)
5
- # Open a long perp position, then close it.
6
-
7
- require_relative 'test_helpers'
8
-
9
- sdk = build_sdk
10
- perp_coin = 'ETH'
11
- separator("TEST 3: Perp Market Roundtrip (#{perp_coin} Long)")
12
-
13
- mids = sdk.info.all_mids
14
- perp_coin_price = mids[perp_coin]&.to_f
15
-
16
- if perp_coin_price&.positive?
17
- meta = sdk.info.meta
18
- perp_coin_meta = meta['universe'].find { |a| a['name'] == perp_coin }
19
- sz_decimals = perp_coin_meta['szDecimals']
20
-
21
- perp_size = (20.0 / perp_coin_price).ceil(sz_decimals)
22
-
23
- puts "#{perp_coin} mid: $#{perp_coin_price.round(2)}"
24
- puts "Size: #{perp_size} #{perp_coin} (~$#{(perp_size * perp_coin_price).round(2)})"
25
- puts "Slippage: #{(PERP_SLIPPAGE * 100).to_i}% (with retry up to #{((PERP_SLIPPAGE + ORACLE_SLIPPAGE_INCREMENT * (ORACLE_RETRY_ATTEMPTS - 1)) * 100).to_i}%)"
26
- puts
27
-
28
- puts 'Opening LONG position (market buy)...'
29
- result = market_order_with_retry(
30
- sdk,
31
- coin: perp_coin,
32
- is_buy: true,
33
- size: perp_size,
34
- base_slippage: PERP_SLIPPAGE
35
- )
36
- open_success = check_result(result, 'Long open')
37
-
38
- if open_success
39
- wait_with_countdown(WAIT_SECONDS, 'Position open. Waiting before close...')
40
-
41
- puts 'Closing LONG position (market sell)...'
42
- result = market_order_with_retry(
43
- sdk,
44
- coin: perp_coin,
45
- is_buy: false,
46
- size: perp_size,
47
- base_slippage: PERP_SLIPPAGE
48
- )
49
- check_result(result, 'Long close')
50
- else
51
- puts red('Skipping close - position was not opened')
52
- end
53
- else
54
- puts red("SKIPPED: Could not get #{perp_coin} price")
55
- end
56
-
57
- test_passed('Test 3 Perp Market Roundtrip')
58
-
@@ -1,52 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Test 4: Perp Limit Order (Short, then Cancel)
5
- # Place a limit sell well above market, then cancel it.
6
-
7
- require_relative 'test_helpers'
8
-
9
- sdk = build_sdk
10
- separator('TEST 4: Perp Limit Order (Short, then Cancel)')
11
-
12
- perp_coin = 'BTC'
13
- mids = sdk.info.all_mids
14
- btc_price = mids[perp_coin]&.to_f
15
-
16
- if btc_price&.positive?
17
- meta = sdk.info.meta
18
- btc_meta = meta['universe'].find { |a| a['name'] == perp_coin }
19
- sz_decimals = btc_meta['szDecimals']
20
-
21
- limit_price = (btc_price * 1.50).round(0).to_i
22
- perp_size = (20.0 / btc_price).ceil(sz_decimals)
23
-
24
- puts "#{perp_coin} mid: $#{btc_price.round(2)}"
25
- puts "Limit price: $#{limit_price} (50% above mid - won't fill)"
26
- puts "Size: #{perp_size} BTC"
27
- puts
28
-
29
- puts 'Placing limit SELL order (short)...'
30
- result = sdk.exchange.order(
31
- coin: perp_coin,
32
- is_buy: false,
33
- size: perp_size,
34
- limit_px: limit_price,
35
- order_type: { limit: { tif: 'Gtc' } },
36
- reduce_only: false
37
- )
38
- oid = check_result(result, 'Limit short')
39
-
40
- if oid.is_a?(Integer)
41
- wait_with_countdown(WAIT_SECONDS, 'Order resting. Waiting before cancel...')
42
-
43
- puts "Canceling order #{oid}..."
44
- result = sdk.exchange.cancel(coin: perp_coin, oid: oid)
45
- check_result(result, 'Cancel')
46
- end
47
- else
48
- puts red("SKIPPED: Could not get #{perp_coin} price")
49
- end
50
-
51
- test_passed('Test 4 Perp Limit Order')
52
-
@@ -1,50 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Test 5: Update Leverage (BTC)
5
- # Set cross, isolated, then reset leverage.
6
- # Requires no open BTC position (cannot switch leverage type with open position).
7
-
8
- require_relative 'test_helpers'
9
-
10
- sdk = build_sdk
11
- separator('TEST 5: Update Leverage (BTC)')
12
-
13
- perp_coin = 'BTC'
14
- mids = sdk.info.all_mids
15
- btc_price = mids[perp_coin]&.to_f
16
-
17
- if btc_price&.positive?
18
- # Check for open position - cannot switch leverage type with open position
19
- unless check_position_and_prompt(sdk, perp_coin, timeout: 10)
20
- puts
21
- puts green('Test 5 Update Leverage skipped (open position).')
22
- exit 0
23
- end
24
-
25
- puts 'Setting BTC to 5x cross leverage...'
26
- result = sdk.exchange.update_leverage(coin: perp_coin, leverage: 5, is_cross: true)
27
- dump_status(result)
28
- api_error?(result) || puts(green('5x cross leverage set'))
29
- puts
30
-
31
- wait_with_countdown(WAIT_SECONDS, 'Waiting before next leverage update...')
32
-
33
- puts 'Setting BTC to 3x isolated leverage...'
34
- result = sdk.exchange.update_leverage(coin: perp_coin, leverage: 3, is_cross: false)
35
- dump_status(result)
36
- api_error?(result) || puts(green('3x isolated leverage set'))
37
- puts
38
-
39
- wait_with_countdown(WAIT_SECONDS, 'Waiting before resetting leverage...')
40
-
41
- puts 'Resetting BTC to 1x cross leverage...'
42
- result = sdk.exchange.update_leverage(coin: perp_coin, leverage: 1, is_cross: true)
43
- dump_status(result)
44
- api_error?(result) || puts(green('1x cross leverage set'))
45
- else
46
- puts red("SKIPPED: Could not get #{perp_coin} price")
47
- end
48
-
49
- test_passed('Test 5 Update Leverage')
50
-
@@ -1,67 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Test 6: Modify Order (BTC)
5
- # Place a limit buy, modify its price, then cancel.
6
-
7
- require_relative 'test_helpers'
8
-
9
- sdk = build_sdk
10
- separator('TEST 6: Modify Order (BTC)')
11
-
12
- perp_coin = 'BTC'
13
- mids = sdk.info.all_mids
14
- btc_price = mids[perp_coin]&.to_f
15
-
16
- if btc_price&.positive?
17
- meta = sdk.info.meta
18
- btc_meta = meta['universe'].find { |a| a['name'] == perp_coin }
19
- sz_decimals = btc_meta['szDecimals']
20
-
21
- original_price = (btc_price * 0.50).round(0).to_i
22
- modified_price = (btc_price * 0.51).round(0).to_i
23
- perp_size = (20.0 / btc_price).ceil(sz_decimals)
24
-
25
- puts "#{perp_coin} mid: $#{btc_price.round(2)}"
26
- puts "Original limit: $#{original_price} (50% below mid)"
27
- puts "Modified limit: $#{modified_price} (49% below mid)"
28
- puts "Size: #{perp_size} BTC"
29
- puts
30
-
31
- puts 'Placing limit BUY order...'
32
- result = sdk.exchange.order(
33
- coin: perp_coin,
34
- is_buy: true,
35
- size: perp_size,
36
- limit_px: original_price,
37
- order_type: { limit: { tif: 'Gtc' } }
38
- )
39
- oid = check_result(result, 'Limit buy')
40
-
41
- if oid.is_a?(Integer)
42
- wait_with_countdown(WAIT_SECONDS, 'Order resting. Waiting before modify...')
43
-
44
- puts "Modifying order #{oid} (price: $#{original_price} -> $#{modified_price})..."
45
- result = sdk.exchange.modify_order(
46
- oid: oid,
47
- coin: perp_coin,
48
- is_buy: true,
49
- size: perp_size,
50
- limit_px: modified_price
51
- )
52
- new_oid = check_result(result, 'Modify')
53
- new_oid = oid unless new_oid.is_a?(Integer)
54
- puts
55
-
56
- wait_with_countdown(WAIT_SECONDS, 'Waiting before cancel...')
57
-
58
- puts "Canceling modified order #{new_oid}..."
59
- result = sdk.exchange.cancel(coin: perp_coin, oid: new_oid)
60
- check_result(result, 'Cancel')
61
- end
62
- else
63
- puts red("SKIPPED: Could not get #{perp_coin} price")
64
- end
65
-
66
- test_passed('Test 6 Modify Order')
67
-
@@ -1,55 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Test 7: Market Close (PERP)
5
- # Open a long position, then close it using market_close (auto-detect size).
6
-
7
- require_relative 'test_helpers'
8
-
9
- sdk = build_sdk
10
- perp_coin = 'ETH'
11
- separator("TEST 7: Market Close (#{perp_coin})")
12
-
13
- mids = sdk.info.all_mids
14
- perp_coin_price = mids[perp_coin]&.to_f
15
-
16
- if perp_coin_price&.positive?
17
- meta = sdk.info.meta
18
- perp_coin_meta = meta['universe'].find { |a| a['name'] == perp_coin }
19
- sz_decimals = perp_coin_meta['szDecimals']
20
-
21
- perp_size = (20.0 / perp_coin_price).ceil(sz_decimals)
22
-
23
- puts "#{perp_coin} mid: $#{perp_coin_price.round(2)}"
24
- puts "Size: #{perp_size} #{perp_coin}"
25
- puts "Slippage: #{(PERP_SLIPPAGE * 100).to_i}% (with retry up to #{((PERP_SLIPPAGE + ORACLE_SLIPPAGE_INCREMENT * (ORACLE_RETRY_ATTEMPTS - 1)) * 100).to_i}%)"
26
- puts
27
-
28
- puts 'Opening LONG position (market buy)...'
29
- result = market_order_with_retry(
30
- sdk,
31
- coin: perp_coin,
32
- is_buy: true,
33
- size: perp_size,
34
- base_slippage: PERP_SLIPPAGE
35
- )
36
- open_success = check_result(result, 'Long open')
37
-
38
- if open_success
39
- wait_with_countdown(WAIT_SECONDS, 'Position open. Waiting before market_close...')
40
-
41
- puts 'Closing position using market_close (auto-detect size)...'
42
- result = sdk.exchange.market_close(
43
- coin: perp_coin,
44
- slippage: PERP_SLIPPAGE + ORACLE_SLIPPAGE_INCREMENT # Use higher slippage for close
45
- )
46
- check_result(result, 'Market close')
47
- else
48
- puts red('Skipping market_close - position was not opened')
49
- end
50
- else
51
- puts red("SKIPPED: Could not get #{perp_coin} price")
52
- end
53
-
54
- test_passed('Test 7 Market Close')
55
-
@@ -1,36 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Test 8: USD Class Transfer (Perp <-> Spot)
5
- # Transfer $10 from perp to spot, then back.
6
-
7
- require_relative 'test_helpers'
8
-
9
- sdk = build_sdk
10
- separator('TEST 8: USD Class Transfer (Perp <-> Spot)')
11
-
12
- # Unified-account wallets cannot use usdClassTransfer — perp and spot balances
13
- # are merged, so the action is disabled at the exchange layer. Detect this up
14
- # front and skip rather than hitting a predictable "Action disabled" failure.
15
- abstraction = sdk.info.user_abstraction(sdk.exchange.address)
16
- if abstraction == 'unifiedAccount'
17
- puts "SKIPPED: Wallet has unified account active (abstraction=#{abstraction.inspect})."
18
- puts ' usdClassTransfer is disabled by the exchange when perp/spot balances are unified.'
19
- test_passed('Test 8 USD Class Transfer')
20
- exit 0
21
- end
22
-
23
- puts 'Transferring $10 from perp to spot...'
24
- result = sdk.exchange.usd_class_transfer(amount: '10', to_perp: false)
25
- dump_status(result)
26
- api_error?(result) || puts(green('Transfer to spot successful!'))
27
- puts
28
-
29
- wait_with_countdown(WAIT_SECONDS, 'Waiting before transferring back...')
30
-
31
- puts 'Transferring $10 from spot to perp...'
32
- result = sdk.exchange.usd_class_transfer(amount: '10', to_perp: true)
33
- dump_status(result)
34
- api_error?(result) || puts(green('Transfer to perp successful!'))
35
-
36
- test_passed('Test 8 USD Class Transfer')
@@ -1,51 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Test 9: Sub Account Lifecycle
5
- # Create a sub-account, deposit $10, then withdraw $10.
6
-
7
- require_relative 'test_helpers'
8
-
9
- sdk = build_sdk
10
- separator('TEST 9: Sub Account Lifecycle')
11
-
12
- puts 'Creating sub-account "ruby-sdk-test"...'
13
- result = sdk.exchange.create_sub_account(name: 'ruby-sdk-test')
14
-
15
- if api_error?(result)
16
- exit 1
17
- end
18
-
19
- puts green('Sub-account created!')
20
- puts
21
-
22
- sub_account_address = result.dig('response', 'data', 'subAccountUser')
23
- if sub_account_address
24
- puts "Sub-account address: #{sub_account_address}"
25
- puts
26
-
27
- wait_with_countdown(WAIT_SECONDS, 'Waiting before deposit...')
28
-
29
- puts 'Depositing $10 to sub-account...'
30
- result = sdk.exchange.sub_account_transfer(
31
- sub_account_user: sub_account_address,
32
- is_deposit: true,
33
- usd: 10
34
- )
35
- api_error?(result) || puts(green('Deposit successful!'))
36
- puts
37
-
38
- wait_with_countdown(WAIT_SECONDS, 'Waiting before withdrawal...')
39
-
40
- puts 'Withdrawing $10 from sub-account...'
41
- result = sdk.exchange.sub_account_transfer(
42
- sub_account_user: sub_account_address,
43
- is_deposit: false,
44
- usd: 10
45
- )
46
- api_error?(result) || puts(green('Withdrawal successful!'))
47
- else
48
- puts red('SKIPPED: Could not extract sub-account address from response')
49
- end
50
-
51
- test_passed('Test 9 Sub Account Lifecycle')
@@ -1,68 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Test 10: Vault Status / Deposit / Withdraw
5
- #
6
- # Default: Show vault status (equity, entry date, unlock date)
7
- # Options: ruby test_10_vault_transfer.rb deposit
8
- # ruby test_10_vault_transfer.rb withdraw
9
-
10
- require_relative 'test_helpers'
11
-
12
- sdk = build_sdk
13
- separator('TEST 10: Vault Status / Deposit / Withdraw')
14
-
15
- vault_addr = '0xa15099a30bbf2e68942d6f4c43d70d04faeab0a0'
16
- action = ARGV[0] # nil, "deposit", or "withdraw"
17
-
18
- puts "Vault: #{vault_addr}"
19
- puts
20
-
21
- vault = sdk.info.vault_details(vault_addr, sdk.exchange.address)
22
- follower = vault['followerState']
23
-
24
- if follower
25
- equity = follower['vaultEquity']
26
- entry_time = follower['vaultEntryTime']
27
- lockup_until = follower['lockupUntil']
28
-
29
- puts "Vault equity: $#{equity}"
30
- puts "Entry date: #{Time.at(entry_time / 1000.0).utc}" if entry_time
31
- puts "Unlock date: #{Time.at(lockup_until / 1000.0).utc}" if lockup_until
32
- else
33
- puts 'No position in this vault.'
34
- end
35
- puts
36
-
37
- case action
38
- when 'deposit'
39
- puts 'Depositing $10 to vault...'
40
- result = sdk.exchange.vault_transfer(
41
- vault_address: vault_addr,
42
- is_deposit: true,
43
- usd: 10
44
- )
45
- dump_status(result)
46
- api_error?(result) || puts(green('Vault deposit successful!'))
47
- when 'withdraw'
48
- equity_f = follower&.dig('vaultEquity')&.to_f || 0
49
- if equity_f > 1
50
- withdraw_amount = equity_f.floor
51
- puts "Withdrawing $#{withdraw_amount} from vault..."
52
- result = sdk.exchange.vault_transfer(
53
- vault_address: vault_addr,
54
- is_deposit: false,
55
- usd: withdraw_amount
56
- )
57
- dump_status(result)
58
- api_error?(result) || puts(green('Vault withdrawal successful!'))
59
- else
60
- puts red("Insufficient vault equity to withdraw ($#{equity_f})")
61
- end
62
- else
63
- puts 'Pass "deposit" or "withdraw" as an argument to perform a transfer.'
64
- puts ' ruby scripts/test_10_vault_transfer.rb deposit'
65
- puts ' ruby scripts/test_10_vault_transfer.rb withdraw'
66
- end
67
-
68
- test_passed('Test 10 Vault Status')
@@ -1,81 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Test 11: Builder Fee (Approve + Order with Builder)
5
- # Approve a builder fee, then place an order with builder param, then cancel.
6
-
7
- require_relative 'test_helpers'
8
-
9
- sdk = build_sdk
10
- separator('TEST 11: Builder Fee (Approve + Order with Builder)')
11
-
12
- builder_address = '0x250F311Ae04D3CEA03443C76340069eD26C47D7D'
13
- max_fee_rate = '0.01%'
14
- perp_coin = 'BTC'
15
-
16
- # Step 1: Approve builder fee
17
- # On testnet, the builder may not meet the exchange's minimum balance for approval.
18
- # That precondition failure is not an SDK bug — we downgrade it to a warning and
19
- # continue, since the order-with-builder-fee flow (the thing we actually ship)
20
- # can still be exercised if a prior approval is in place.
21
- puts "Approving builder fee for #{builder_address} (max #{max_fee_rate})..."
22
- result = sdk.exchange.approve_builder_fee(builder: builder_address, max_fee_rate: max_fee_rate)
23
- if result.is_a?(Hash) && result['status'] == 'err' &&
24
- result['response'].to_s.include?('insufficient balance')
25
- puts red("WARNING: #{result['response']}")
26
- puts ' Continuing — this is a testnet precondition, not an SDK failure.'
27
- else
28
- dump_status(result)
29
- api_error?(result) || puts(green('Builder fee approved'))
30
- end
31
- puts
32
-
33
- wait_with_countdown(WAIT_SECONDS, 'Waiting before placing order with builder...')
34
-
35
- # Step 2: Verify approval via Info API
36
- puts 'Checking builder fee approval...'
37
- approval = sdk.info.max_builder_fee(sdk.exchange.address, builder_address)
38
- puts "Max builder fee: #{approval.inspect}"
39
- puts
40
-
41
- # Step 3: Place order with builder param
42
- mids = sdk.info.all_mids
43
- btc_price = mids[perp_coin]&.to_f
44
-
45
- if btc_price&.positive?
46
- meta = sdk.info.meta
47
- btc_meta = meta['universe'].find { |a| a['name'] == perp_coin }
48
- sz_decimals = btc_meta['szDecimals']
49
-
50
- limit_price = (btc_price * 1.50).round(0).to_i
51
- perp_size = (20.0 / btc_price).ceil(sz_decimals)
52
-
53
- puts "#{perp_coin} mid: $#{btc_price.round(2)}"
54
- puts "Limit price: $#{limit_price} (50% above mid - won't fill)"
55
- puts "Size: #{perp_size} BTC"
56
- puts "Builder: #{builder_address} (fee: 10 = 1bp)"
57
- puts
58
-
59
- puts 'Placing limit SELL order with builder fee...'
60
- result = sdk.exchange.order(
61
- coin: perp_coin,
62
- is_buy: false,
63
- size: perp_size,
64
- limit_px: limit_price,
65
- order_type: { limit: { tif: 'Gtc' } },
66
- builder: { b: builder_address, f: 10 }
67
- )
68
- oid = check_result(result, 'Limit short with builder')
69
-
70
- if oid.is_a?(Integer)
71
- wait_with_countdown(WAIT_SECONDS, 'Order resting. Waiting before cancel...')
72
-
73
- puts "Canceling order #{oid}..."
74
- result = sdk.exchange.cancel(coin: perp_coin, oid: oid)
75
- check_result(result, 'Cancel')
76
- end
77
- else
78
- puts red("SKIPPED: Could not get #{perp_coin} price")
79
- end
80
-
81
- test_passed('Test 11 Builder Fee')
@@ -1,70 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Test 12: Staking Status / Delegate / Undelegate
5
- #
6
- # Default: Show staking summary and delegations
7
- # Options: ruby test_12_staking.rb delegate
8
- # ruby test_12_staking.rb undelegate
9
-
10
- require_relative 'test_helpers'
11
-
12
- sdk = build_sdk
13
- separator('TEST 12: Staking Status / Delegate / Undelegate')
14
-
15
- validator = '0x946bf3135c7d15e4462b510f74b6e304aabb5b21'
16
- action = ARGV[0] # nil, "delegate", or "undelegate"
17
- delegate_amount = 10_000_000 # 0.1 HYPE (wei = float * 1e8)
18
-
19
- puts "Validator: #{validator}"
20
- puts
21
-
22
- # Show staking summary
23
- summary = sdk.info.delegator_summary(sdk.exchange.address)
24
- puts "Staking summary:"
25
- puts " Delegated: #{summary['delegated']}" if summary['delegated']
26
- puts " Undelegatable: #{summary['undelegatable']}" if summary['undelegatable']
27
- puts " Total pending: #{summary['totalPending']}" if summary['totalPending']
28
- puts " N delegations: #{summary['nDelegations']}" if summary['nDelegations']
29
- puts
30
-
31
- # Show delegations for this validator
32
- delegations = sdk.info.delegations(sdk.exchange.address)
33
- validator_delegation = delegations&.find { |d| d['validator']&.downcase == validator.downcase }
34
-
35
- if validator_delegation
36
- puts "Delegation to #{validator}:"
37
- puts " Amount: #{validator_delegation['amount']}"
38
- lockup = validator_delegation['lockedUntilTimestamp']
39
- puts " Locked until: #{Time.at(lockup / 1000.0).utc}" if lockup
40
- else
41
- puts "No active delegation to #{validator}."
42
- end
43
- puts
44
-
45
- case action
46
- when 'delegate'
47
- puts "Delegating 0.1 HYPE to #{validator}..."
48
- result = sdk.exchange.token_delegate(
49
- validator: validator,
50
- wei: delegate_amount,
51
- is_undelegate: false
52
- )
53
- dump_status(result)
54
- api_error?(result) || puts(green('Delegation successful!'))
55
- when 'undelegate'
56
- puts "Undelegating 0.1 HYPE from #{validator}..."
57
- result = sdk.exchange.token_delegate(
58
- validator: validator,
59
- wei: delegate_amount,
60
- is_undelegate: true
61
- )
62
- dump_status(result)
63
- api_error?(result) || puts(green('Undelegation successful!'))
64
- else
65
- puts 'Pass "delegate" or "undelegate" as an argument to perform an action.'
66
- puts ' ruby scripts/test_12_staking.rb delegate'
67
- puts ' ruby scripts/test_12_staking.rb undelegate'
68
- end
69
-
70
- test_passed('Test 12 Staking')
@@ -1,83 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Test 13: WebSocket l2Book Subscription
5
- #
6
- # Subscribes to ETH perp l2Book on testnet, prints 3 updates
7
- # with top-of-book (best bid/ask), then cleanly disconnects.
8
- #
9
- # No private key required (read-only WebSocket).
10
- #
11
- # Usage:
12
- # ruby scripts/test_13_ws_l2_book.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 13: WebSocket l2Book Subscription'
27
- puts '=' * 60
28
- puts
29
- puts 'Network: Testnet'
30
- puts 'Subscribing to ETH l2Book (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: 'l2Book', coin: 'ETH' }) do |data|
42
- mutex.synchronize do
43
- next if updates.length >= 3
44
-
45
- levels = data['levels']
46
- bids = levels&.dig(0) || []
47
- asks = levels&.dig(1) || []
48
- best_bid = bids.first
49
- best_ask = asks.first
50
-
51
- update_num = updates.length + 1
52
- puts "Update #{update_num}/3:"
53
- puts " Best bid: #{best_bid ? "#{best_bid['px']} (#{best_bid['sz']})" : 'n/a'}"
54
- puts " Best ask: #{best_ask ? "#{best_ask['px']} (#{best_ask['sz']})" : 'n/a'}"
55
- puts " Bid levels: #{bids.length}, Ask levels: #{asks.length}"
56
- puts
57
-
58
- updates << data
59
- done.signal if updates.length >= 3
60
- end
61
- end
62
-
63
- # Wait for 3 updates or timeout after 30 seconds
64
- success = false
65
- mutex.synchronize do
66
- deadline = Time.now + 30
67
- while updates.length < 3 && Time.now < deadline
68
- remaining = deadline - Time.now
69
- break if remaining <= 0
70
-
71
- done.wait(mutex, remaining)
72
- end
73
- success = updates.length >= 3
74
- end
75
-
76
- sdk.ws.close
77
-
78
- if success
79
- puts green('Test 13 WebSocket l2Book passed!')
80
- else
81
- puts red("Test 13 FAILED: only received #{updates.length}/3 updates within 30s")
82
- exit 1
83
- end