hyperliquid 0.4.1 → 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.
@@ -0,0 +1,52 @@
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
+
@@ -0,0 +1,39 @@
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
+
7
+ require_relative 'test_helpers'
8
+
9
+ sdk = build_sdk
10
+ separator('TEST 5: Update Leverage (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
+ puts 'Setting BTC to 5x cross leverage...'
18
+ result = sdk.exchange.update_leverage(coin: perp_coin, leverage: 5, is_cross: true)
19
+ api_error?(result) || puts(green('5x cross leverage set'))
20
+ puts
21
+
22
+ wait_with_countdown(WAIT_SECONDS, 'Waiting before next leverage update...')
23
+
24
+ puts 'Setting BTC to 3x isolated leverage...'
25
+ result = sdk.exchange.update_leverage(coin: perp_coin, leverage: 3, is_cross: false)
26
+ api_error?(result) || puts(green('3x isolated leverage set'))
27
+ puts
28
+
29
+ wait_with_countdown(WAIT_SECONDS, 'Waiting before resetting leverage...')
30
+
31
+ puts 'Resetting BTC to 1x cross leverage...'
32
+ result = sdk.exchange.update_leverage(coin: perp_coin, leverage: 1, is_cross: true)
33
+ api_error?(result) || puts(green('1x cross leverage set'))
34
+ else
35
+ puts red("SKIPPED: Could not get #{perp_coin} price")
36
+ end
37
+
38
+ test_passed('Test 5 Update Leverage')
39
+
@@ -0,0 +1,67 @@
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
+
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Test 7: Market Close (BTC)
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
+ separator('TEST 7: Market Close (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
+ perp_size = (20.0 / btc_price).ceil(sz_decimals)
22
+
23
+ puts "#{perp_coin} mid: $#{btc_price.round(2)}"
24
+ puts "Size: #{perp_size} BTC"
25
+ puts
26
+
27
+ puts 'Opening LONG position (market buy)...'
28
+ result = sdk.exchange.market_order(
29
+ coin: perp_coin,
30
+ is_buy: true,
31
+ size: perp_size,
32
+ slippage: PERP_SLIPPAGE
33
+ )
34
+ check_result(result, 'Long open')
35
+
36
+ wait_with_countdown(WAIT_SECONDS, 'Position open. Waiting before market_close...')
37
+
38
+ puts 'Closing position using market_close (auto-detect size)...'
39
+ result = sdk.exchange.market_close(
40
+ coin: perp_coin,
41
+ slippage: PERP_SLIPPAGE
42
+ )
43
+ check_result(result, 'Market close')
44
+ else
45
+ puts red("SKIPPED: Could not get #{perp_coin} price")
46
+ end
47
+
48
+ test_passed('Test 7 Market Close')
49
+
@@ -0,0 +1,23 @@
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
+ puts 'Transferring $10 from perp to spot...'
13
+ result = sdk.exchange.usd_class_transfer(amount: '10', to_perp: false)
14
+ api_error?(result) || puts(green('Transfer to spot successful!'))
15
+ puts
16
+
17
+ wait_with_countdown(WAIT_SECONDS, 'Waiting before transferring back...')
18
+
19
+ puts 'Transferring $10 from spot to perp...'
20
+ result = sdk.exchange.usd_class_transfer(amount: '10', to_perp: true)
21
+ api_error?(result) || puts(green('Transfer to perp successful!'))
22
+
23
+ test_passed('Test 8 USD Class Transfer')
@@ -0,0 +1,51 @@
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')
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Test 10: Vault Deposit/Withdraw
5
+ # Check vault equity and either deposit or withdraw.
6
+
7
+ require_relative 'test_helpers'
8
+
9
+ sdk = build_sdk
10
+ separator('TEST 10: Vault Deposit/Withdraw')
11
+
12
+ vault_addr = '0xa15099a30bbf2e68942d6f4c43d70d04faeab0a0'
13
+ puts "Vault: #{vault_addr}"
14
+ puts
15
+
16
+ vault = sdk.info.vault_details(vault_addr, sdk.exchange.address)
17
+ user_vault_equity = vault.dig('portfolio', 0, 1, 'accountValue')&.to_f || 0
18
+ puts "User equity in vault: $#{user_vault_equity}"
19
+ puts
20
+
21
+ if user_vault_equity > 1
22
+ withdraw_amount = user_vault_equity.floor
23
+ puts "Withdrawing $#{withdraw_amount} from vault..."
24
+ result = sdk.exchange.vault_transfer(
25
+ vault_address: vault_addr,
26
+ is_deposit: false,
27
+ usd: withdraw_amount
28
+ )
29
+ api_error?(result) || puts(green('Vault withdrawal successful!'))
30
+ else
31
+ puts "User has $#{user_vault_equity} in vault (insufficient to withdraw)"
32
+ puts 'Depositing $10 to vault...'
33
+ result = sdk.exchange.vault_transfer(
34
+ vault_address: vault_addr,
35
+ is_deposit: true,
36
+ usd: 10
37
+ )
38
+ api_error?(result) || puts(green('Vault deposit successful!'))
39
+ end
40
+
41
+ test_passed('Test 10 Vault Deposit/Withdraw')
@@ -0,0 +1,86 @@
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_transfer.rb'
32
+ ].freeze
33
+
34
+ def green(text)
35
+ "\e[32m#{text}\e[0m"
36
+ end
37
+
38
+ def red(text)
39
+ "\e[31m#{text}\e[0m"
40
+ end
41
+
42
+ unless ENV['HYPERLIQUID_PRIVATE_KEY']
43
+ puts red('Error: Set HYPERLIQUID_PRIVATE_KEY environment variable')
44
+ puts 'Usage: HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_all.rb'
45
+ exit 1
46
+ end
47
+
48
+ scripts_dir = __dir__
49
+ passed = []
50
+ failed = []
51
+
52
+ SCRIPTS.each do |script|
53
+ path = File.join(scripts_dir, script)
54
+ puts
55
+ puts '#' * 60
56
+ puts "# Running: #{script}"
57
+ puts '#' * 60
58
+
59
+ success = system(RbConfig.ruby, path)
60
+
61
+ if success
62
+ passed << script
63
+ else
64
+ failed << script
65
+ puts red("!!! #{script} exited with error !!!")
66
+ end
67
+ end
68
+
69
+ puts
70
+ puts '=' * 60
71
+ puts 'INTEGRATION TEST SUMMARY'
72
+ puts '=' * 60
73
+ puts
74
+ puts "Passed: #{passed.length}/#{SCRIPTS.length}"
75
+ passed.each { |s| puts green(" [PASS] #{s}") }
76
+ if failed.any?
77
+ puts
78
+ puts "Failed: #{failed.length}/#{SCRIPTS.length}"
79
+ failed.each { |s| puts red(" [FAIL] #{s}") }
80
+ end
81
+ puts
82
+ puts 'Check your testnet wallet for trade history:'
83
+ puts 'https://app.hyperliquid-testnet.xyz'
84
+ puts
85
+
86
+ exit(failed.empty? ? 0 : 1)
@@ -0,0 +1,100 @@
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.05 # 5% for perp markets
9
+
10
+ def green(text)
11
+ "\e[32m#{text}\e[0m"
12
+ end
13
+
14
+ def red(text)
15
+ "\e[31m#{text}\e[0m"
16
+ end
17
+
18
+ $test_failed = false
19
+
20
+ def separator(title)
21
+ puts
22
+ puts '=' * 60
23
+ puts title
24
+ puts '=' * 60
25
+ puts
26
+ end
27
+
28
+ def wait_with_countdown(seconds, message)
29
+ puts message
30
+ seconds.downto(1) do |i|
31
+ print "\r #{i} seconds remaining... "
32
+ sleep 1
33
+ end
34
+ puts "\r Done! "
35
+ puts
36
+ end
37
+
38
+ def api_error?(result)
39
+ return false unless result.is_a?(Hash) && result['status'] == 'err'
40
+
41
+ $test_failed = true
42
+ puts red("FAILED: #{result['response']}")
43
+ true
44
+ end
45
+
46
+ def check_result(result, operation)
47
+ return false if api_error?(result)
48
+
49
+ status = result.dig('response', 'data', 'statuses', 0)
50
+
51
+ if status.is_a?(Hash) && status['error']
52
+ $test_failed = true
53
+ puts red("FAILED: #{status['error']}")
54
+ return false
55
+ end
56
+
57
+ if status.is_a?(Hash) && status['resting']
58
+ puts green("Order resting with OID: #{status['resting']['oid']}")
59
+ return status['resting']['oid']
60
+ end
61
+
62
+ if status == 'success' || (status.is_a?(Hash) && status['filled'])
63
+ puts green("#{operation} successful!")
64
+ return true
65
+ end
66
+
67
+ puts "Result: #{status.inspect}"
68
+ true
69
+ end
70
+
71
+ def test_passed(name)
72
+ puts
73
+ if $test_failed
74
+ puts red("#{name} FAILED!")
75
+ exit 1
76
+ else
77
+ puts green("#{name} passed!")
78
+ end
79
+ end
80
+
81
+ def build_sdk
82
+ private_key = ENV['HYPERLIQUID_PRIVATE_KEY']
83
+ unless private_key
84
+ puts red('Error: Set HYPERLIQUID_PRIVATE_KEY environment variable')
85
+ puts 'Usage: HYPERLIQUID_PRIVATE_KEY=0x... ruby <script>'
86
+ exit 1
87
+ end
88
+
89
+ sdk = Hyperliquid.new(
90
+ testnet: true,
91
+ private_key: private_key
92
+ )
93
+
94
+ puts "Wallet: #{sdk.exchange.address}"
95
+ puts 'Network: Testnet'
96
+ puts "Testnet UI: https://app.hyperliquid-testnet.xyz"
97
+ puts
98
+
99
+ sdk
100
+ end