hyperliquid 0.5.0 → 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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +13 -1
- data/CLAUDE.md +6 -7
- data/docs/API.md +14 -1
- data/docs/DEVELOPMENT.md +31 -17
- data/docs/EXAMPLES.md +91 -0
- data/lib/hyperliquid/exchange.rb +195 -0
- data/lib/hyperliquid/signing/eip712.rb +73 -0
- data/lib/hyperliquid/signing/signer.rb +27 -2
- data/lib/hyperliquid/version.rb +1 -1
- data/scripts/test_01_spot_market_roundtrip.rb +48 -0
- data/scripts/test_02_spot_limit_order.rb +48 -0
- data/scripts/test_03_perp_market_roundtrip.rb +52 -0
- data/scripts/test_04_perp_limit_order.rb +52 -0
- data/scripts/test_05_update_leverage.rb +39 -0
- data/scripts/test_06_modify_order.rb +67 -0
- data/scripts/test_07_market_close.rb +49 -0
- data/scripts/test_08_usd_class_transfer.rb +23 -0
- data/scripts/test_09_sub_account_lifecycle.rb +51 -0
- data/scripts/test_10_vault_transfer.rb +41 -0
- data/scripts/test_all.rb +86 -0
- data/scripts/test_helpers.rb +100 -0
- data/test_integration.rb +8 -367
- metadata +13 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Test 2: Spot Limit Order (Place and Cancel)
|
|
5
|
+
# Place a limit buy well below market, then cancel it.
|
|
6
|
+
|
|
7
|
+
require_relative 'test_helpers'
|
|
8
|
+
|
|
9
|
+
sdk = build_sdk
|
|
10
|
+
separator('TEST 2: Spot Limit Order (Place and Cancel)')
|
|
11
|
+
|
|
12
|
+
spot_coin = 'PURR/USDC'
|
|
13
|
+
spot_size = 5
|
|
14
|
+
|
|
15
|
+
mids = sdk.info.all_mids
|
|
16
|
+
spot_price = mids[spot_coin]&.to_f
|
|
17
|
+
|
|
18
|
+
if spot_price&.positive?
|
|
19
|
+
limit_price = (spot_price * 0.50).round(2)
|
|
20
|
+
puts "#{spot_coin} mid: $#{spot_price}"
|
|
21
|
+
puts "Limit price: $#{limit_price} (50% below mid - won't fill)"
|
|
22
|
+
puts "Size: #{spot_size} PURR"
|
|
23
|
+
puts
|
|
24
|
+
|
|
25
|
+
puts 'Placing limit BUY order...'
|
|
26
|
+
result = sdk.exchange.order(
|
|
27
|
+
coin: spot_coin,
|
|
28
|
+
is_buy: true,
|
|
29
|
+
size: spot_size,
|
|
30
|
+
limit_px: limit_price,
|
|
31
|
+
order_type: { limit: { tif: 'Gtc' } },
|
|
32
|
+
reduce_only: false
|
|
33
|
+
)
|
|
34
|
+
oid = check_result(result, 'Limit order')
|
|
35
|
+
|
|
36
|
+
if oid.is_a?(Integer)
|
|
37
|
+
wait_with_countdown(WAIT_SECONDS, 'Order resting. Waiting before cancel...')
|
|
38
|
+
|
|
39
|
+
puts "Canceling order #{oid}..."
|
|
40
|
+
result = sdk.exchange.cancel(coin: spot_coin, oid: oid)
|
|
41
|
+
check_result(result, 'Cancel')
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
puts red("SKIPPED: Could not get #{spot_coin} price")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
test_passed('Test 2 Spot Limit Order')
|
|
48
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Test 3: Perp Market Roundtrip (BTC Long)
|
|
5
|
+
# Open a long BTC position, then close it.
|
|
6
|
+
|
|
7
|
+
require_relative 'test_helpers'
|
|
8
|
+
|
|
9
|
+
sdk = build_sdk
|
|
10
|
+
separator('TEST 3: Perp Market Roundtrip (BTC Long)')
|
|
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 (~$#{(perp_size * btc_price).round(2)})"
|
|
25
|
+
puts "Slippage: #{(PERP_SLIPPAGE * 100).to_i}%"
|
|
26
|
+
puts
|
|
27
|
+
|
|
28
|
+
puts 'Opening LONG position (market buy)...'
|
|
29
|
+
result = sdk.exchange.market_order(
|
|
30
|
+
coin: perp_coin,
|
|
31
|
+
is_buy: true,
|
|
32
|
+
size: perp_size,
|
|
33
|
+
slippage: PERP_SLIPPAGE
|
|
34
|
+
)
|
|
35
|
+
check_result(result, 'Long open')
|
|
36
|
+
|
|
37
|
+
wait_with_countdown(WAIT_SECONDS, 'Position open. Waiting before close...')
|
|
38
|
+
|
|
39
|
+
puts 'Closing LONG position (market sell)...'
|
|
40
|
+
result = sdk.exchange.market_order(
|
|
41
|
+
coin: perp_coin,
|
|
42
|
+
is_buy: false,
|
|
43
|
+
size: perp_size,
|
|
44
|
+
slippage: PERP_SLIPPAGE
|
|
45
|
+
)
|
|
46
|
+
check_result(result, 'Long close')
|
|
47
|
+
else
|
|
48
|
+
puts red("SKIPPED: Could not get #{perp_coin} price")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
test_passed('Test 3 Perp Market Roundtrip')
|
|
52
|
+
|
|
@@ -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')
|
data/scripts/test_all.rb
ADDED
|
@@ -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
|