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,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')
|
data/scripts/test_10_vault.rb
DELETED
|
@@ -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')
|
data/scripts/test_12_staking.rb
DELETED
|
@@ -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
|