hyperliquid 0.6.0 → 1.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/.rubocop.yml +2 -1
- data/CHANGELOG.md +50 -0
- data/README.md +2 -1
- data/docs/API.md +68 -2
- data/docs/EXAMPLES.md +247 -0
- data/docs/WS.md +49 -0
- data/lib/hyperliquid/constants.rb +5 -0
- data/lib/hyperliquid/errors.rb +3 -0
- data/lib/hyperliquid/exchange.rb +203 -19
- data/lib/hyperliquid/info.rb +26 -2
- data/lib/hyperliquid/signing/eip712.rb +37 -0
- data/lib/hyperliquid/signing/signer.rb +6 -0
- data/lib/hyperliquid/version.rb +1 -1
- data/lib/hyperliquid/ws/client.rb +340 -0
- data/lib/hyperliquid.rb +4 -1
- data/scripts/test_03_perp_market_roundtrip.rb +32 -26
- data/scripts/test_05_update_leverage.rb +11 -0
- data/scripts/test_07_market_close.rb +28 -22
- data/scripts/test_08_usd_class_transfer.rb +2 -0
- data/scripts/test_10_vault.rb +68 -0
- data/scripts/test_11_builder_fee.rb +71 -0
- data/scripts/test_12_staking.rb +70 -0
- data/scripts/test_13_ws_l2_book.rb +83 -0
- data/scripts/test_14_ws_candle.rb +81 -0
- data/scripts/test_all.rb +5 -1
- data/scripts/test_helpers.rb +131 -1
- metadata +22 -2
- data/scripts/test_10_vault_transfer.rb +0 -41
data/lib/hyperliquid/exchange.rb
CHANGED
|
@@ -16,11 +16,13 @@ module Hyperliquid
|
|
|
16
16
|
# @param client [Hyperliquid::Client] HTTP client
|
|
17
17
|
# @param signer [Hyperliquid::Signing::Signer] EIP-712 signer
|
|
18
18
|
# @param info [Hyperliquid::Info] Info API client for metadata
|
|
19
|
+
# @param testnet [Boolean] Whether targeting testnet (default: false)
|
|
19
20
|
# @param expires_after [Integer, nil] Optional global expiration timestamp
|
|
20
|
-
def initialize(client:, signer:, info:, expires_after: nil)
|
|
21
|
+
def initialize(client:, signer:, info:, testnet: false, expires_after: nil)
|
|
21
22
|
@client = client
|
|
22
23
|
@signer = signer
|
|
23
24
|
@info = info
|
|
25
|
+
@testnet = testnet
|
|
24
26
|
@expires_after = expires_after
|
|
25
27
|
@asset_cache = nil
|
|
26
28
|
end
|
|
@@ -40,9 +42,10 @@ module Hyperliquid
|
|
|
40
42
|
# @param reduce_only [Boolean] Reduce-only flag (default: false)
|
|
41
43
|
# @param cloid [Cloid, String, nil] Client order ID (optional)
|
|
42
44
|
# @param vault_address [String, nil] Vault address for vault trading (optional)
|
|
45
|
+
# @param builder [Hash, nil] Builder fee config { b: "0xaddress", f: fee_in_tenths_of_bp } (optional)
|
|
43
46
|
# @return [Hash] Order response
|
|
44
47
|
def order(coin:, is_buy:, size:, limit_px:, order_type: { limit: { tif: 'Gtc' } },
|
|
45
|
-
reduce_only: false, cloid: nil, vault_address: nil)
|
|
48
|
+
reduce_only: false, cloid: nil, vault_address: nil, builder: nil)
|
|
46
49
|
nonce = timestamp_ms
|
|
47
50
|
|
|
48
51
|
order_wire = build_order_wire(
|
|
@@ -60,6 +63,7 @@ module Hyperliquid
|
|
|
60
63
|
orders: [order_wire],
|
|
61
64
|
grouping: 'na'
|
|
62
65
|
}
|
|
66
|
+
action[:builder] = normalize_builder(builder) if builder
|
|
63
67
|
|
|
64
68
|
signature = @signer.sign_l1_action(
|
|
65
69
|
action, nonce,
|
|
@@ -74,8 +78,9 @@ module Hyperliquid
|
|
|
74
78
|
# :coin, :is_buy, :size, :limit_px, :order_type, :reduce_only, :cloid
|
|
75
79
|
# @param grouping [String] Order grouping ("na", "normalTpsl", "positionTpsl")
|
|
76
80
|
# @param vault_address [String, nil] Vault address for vault trading (optional)
|
|
81
|
+
# @param builder [Hash, nil] Builder fee config { b: "0xaddress", f: fee_in_tenths_of_bp } (optional)
|
|
77
82
|
# @return [Hash] Bulk order response
|
|
78
|
-
def bulk_orders(orders:, grouping: 'na', vault_address: nil)
|
|
83
|
+
def bulk_orders(orders:, grouping: 'na', vault_address: nil, builder: nil)
|
|
79
84
|
nonce = timestamp_ms
|
|
80
85
|
|
|
81
86
|
order_wires = orders.map do |o|
|
|
@@ -95,6 +100,7 @@ module Hyperliquid
|
|
|
95
100
|
orders: order_wires,
|
|
96
101
|
grouping: grouping
|
|
97
102
|
}
|
|
103
|
+
action[:builder] = normalize_builder(builder) if builder
|
|
98
104
|
|
|
99
105
|
signature = @signer.sign_l1_action(
|
|
100
106
|
action, nonce,
|
|
@@ -110,10 +116,12 @@ module Hyperliquid
|
|
|
110
116
|
# @param size [String, Numeric] Order size
|
|
111
117
|
# @param slippage [Float] Slippage tolerance (default: 0.05 = 5%)
|
|
112
118
|
# @param vault_address [String, nil] Vault address for vault trading (optional)
|
|
119
|
+
# @param builder [Hash, nil] Builder fee config { b: "0xaddress", f: fee_in_tenths_of_bp } (optional)
|
|
113
120
|
# @return [Hash] Order response
|
|
114
|
-
def market_order(coin:, is_buy:, size:, slippage: DEFAULT_SLIPPAGE, vault_address: nil)
|
|
115
|
-
# Get current mid price
|
|
116
|
-
|
|
121
|
+
def market_order(coin:, is_buy:, size:, slippage: DEFAULT_SLIPPAGE, vault_address: nil, builder: nil)
|
|
122
|
+
# Get current mid price (use dex-specific endpoint for HIP-3 assets)
|
|
123
|
+
dex_prefix = extract_dex_prefix(coin)
|
|
124
|
+
mids = dex_prefix ? @info.all_mids(dex: dex_prefix) : @info.all_mids
|
|
117
125
|
mid = mids[coin]&.to_f
|
|
118
126
|
raise ArgumentError, "Unknown asset or no price available: #{coin}" unless mid&.positive?
|
|
119
127
|
|
|
@@ -126,7 +134,8 @@ module Hyperliquid
|
|
|
126
134
|
size: size,
|
|
127
135
|
limit_px: slippage_price,
|
|
128
136
|
order_type: { limit: { tif: 'Ioc' } },
|
|
129
|
-
vault_address: vault_address
|
|
137
|
+
vault_address: vault_address,
|
|
138
|
+
builder: builder
|
|
130
139
|
)
|
|
131
140
|
end
|
|
132
141
|
|
|
@@ -342,10 +351,12 @@ module Hyperliquid
|
|
|
342
351
|
# @param slippage [Float] Slippage tolerance (default: 5%)
|
|
343
352
|
# @param cloid [Cloid, String, nil] Client order ID (optional)
|
|
344
353
|
# @param vault_address [String, nil] Vault address for vault trading (optional)
|
|
354
|
+
# @param builder [Hash, nil] Builder fee config { b: "0xaddress", f: fee_in_tenths_of_bp } (optional)
|
|
345
355
|
# @return [Hash] Order response
|
|
346
|
-
def market_close(coin:, size: nil, slippage: DEFAULT_SLIPPAGE, cloid: nil, vault_address: nil)
|
|
356
|
+
def market_close(coin:, size: nil, slippage: DEFAULT_SLIPPAGE, cloid: nil, vault_address: nil, builder: nil)
|
|
347
357
|
address = vault_address || @signer.address
|
|
348
|
-
|
|
358
|
+
dex_prefix = extract_dex_prefix(coin)
|
|
359
|
+
state = dex_prefix ? @info.user_state(address, dex: dex_prefix) : @info.user_state(address)
|
|
349
360
|
|
|
350
361
|
position = state['assetPositions']&.find do |pos|
|
|
351
362
|
pos.dig('position', 'coin') == coin
|
|
@@ -356,7 +367,7 @@ module Hyperliquid
|
|
|
356
367
|
is_buy = szi.negative?
|
|
357
368
|
close_size = size || szi.abs
|
|
358
369
|
|
|
359
|
-
mids = @info.all_mids
|
|
370
|
+
mids = dex_prefix ? @info.all_mids(dex: dex_prefix) : @info.all_mids
|
|
360
371
|
mid = mids[coin]&.to_f
|
|
361
372
|
raise ArgumentError, "Unknown asset or no price available: #{coin}" unless mid&.positive?
|
|
362
373
|
|
|
@@ -370,7 +381,8 @@ module Hyperliquid
|
|
|
370
381
|
order_type: { limit: { tif: 'Ioc' } },
|
|
371
382
|
reduce_only: true,
|
|
372
383
|
cloid: cloid,
|
|
373
|
-
vault_address: vault_address
|
|
384
|
+
vault_address: vault_address,
|
|
385
|
+
builder: builder
|
|
374
386
|
)
|
|
375
387
|
end
|
|
376
388
|
|
|
@@ -383,7 +395,7 @@ module Hyperliquid
|
|
|
383
395
|
action = {
|
|
384
396
|
type: 'usdSend',
|
|
385
397
|
signatureChainId: '0x66eee',
|
|
386
|
-
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @
|
|
398
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
387
399
|
destination: destination,
|
|
388
400
|
amount: amount.to_s,
|
|
389
401
|
time: nonce
|
|
@@ -406,7 +418,7 @@ module Hyperliquid
|
|
|
406
418
|
action = {
|
|
407
419
|
type: 'spotSend',
|
|
408
420
|
signatureChainId: '0x66eee',
|
|
409
|
-
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @
|
|
421
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
410
422
|
destination: destination,
|
|
411
423
|
token: token,
|
|
412
424
|
amount: amount.to_s,
|
|
@@ -429,7 +441,7 @@ module Hyperliquid
|
|
|
429
441
|
action = {
|
|
430
442
|
type: 'usdClassTransfer',
|
|
431
443
|
signatureChainId: '0x66eee',
|
|
432
|
-
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @
|
|
444
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
433
445
|
amount: amount.to_s,
|
|
434
446
|
toPerp: to_perp,
|
|
435
447
|
nonce: nonce
|
|
@@ -451,7 +463,7 @@ module Hyperliquid
|
|
|
451
463
|
action = {
|
|
452
464
|
type: 'withdraw3',
|
|
453
465
|
signatureChainId: '0x66eee',
|
|
454
|
-
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @
|
|
466
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
455
467
|
destination: destination,
|
|
456
468
|
amount: amount.to_s,
|
|
457
469
|
time: nonce
|
|
@@ -476,7 +488,7 @@ module Hyperliquid
|
|
|
476
488
|
action = {
|
|
477
489
|
type: 'sendAsset',
|
|
478
490
|
signatureChainId: '0x66eee',
|
|
479
|
-
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @
|
|
491
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
480
492
|
destination: destination,
|
|
481
493
|
sourceDex: source_dex,
|
|
482
494
|
destinationDex: destination_dex,
|
|
@@ -569,6 +581,116 @@ module Hyperliquid
|
|
|
569
581
|
post_action(action, signature, nonce, nil)
|
|
570
582
|
end
|
|
571
583
|
|
|
584
|
+
# Authorize an agent wallet to trade on behalf of this account
|
|
585
|
+
# @param agent_address [String] Agent's Ethereum address
|
|
586
|
+
# @param agent_name [String, nil] Optional agent name (omitted from action if nil)
|
|
587
|
+
# @return [Hash] Approve agent response
|
|
588
|
+
def approve_agent(agent_address:, agent_name: nil)
|
|
589
|
+
nonce = timestamp_ms
|
|
590
|
+
action = {
|
|
591
|
+
type: 'approveAgent',
|
|
592
|
+
signatureChainId: '0x66eee',
|
|
593
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
594
|
+
agentAddress: agent_address,
|
|
595
|
+
nonce: nonce
|
|
596
|
+
}
|
|
597
|
+
# agentName is always included in the signed message (empty string if nil),
|
|
598
|
+
# but only included in the posted action if a name was provided (matches Python SDK)
|
|
599
|
+
action[:agentName] = agent_name if agent_name
|
|
600
|
+
signature = @signer.sign_user_signed_action(
|
|
601
|
+
{ agentAddress: agent_address, agentName: agent_name || '', nonce: nonce },
|
|
602
|
+
'HyperliquidTransaction:ApproveAgent',
|
|
603
|
+
Signing::EIP712::APPROVE_AGENT_TYPES
|
|
604
|
+
)
|
|
605
|
+
post_action(action, signature, nonce, nil)
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
# Approve a builder fee rate for a builder address
|
|
609
|
+
# Users must approve a builder before orders with that builder can be placed.
|
|
610
|
+
# @param builder [String] Builder's Ethereum address
|
|
611
|
+
# @param max_fee_rate [String] Maximum fee rate (e.g., "0.01%" for 1 basis point)
|
|
612
|
+
# @return [Hash] Approve builder fee response
|
|
613
|
+
def approve_builder_fee(builder:, max_fee_rate:)
|
|
614
|
+
nonce = timestamp_ms
|
|
615
|
+
action = {
|
|
616
|
+
type: 'approveBuilderFee',
|
|
617
|
+
signatureChainId: '0x66eee',
|
|
618
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
619
|
+
maxFeeRate: max_fee_rate,
|
|
620
|
+
builder: builder,
|
|
621
|
+
nonce: nonce
|
|
622
|
+
}
|
|
623
|
+
signature = @signer.sign_user_signed_action(
|
|
624
|
+
{ maxFeeRate: max_fee_rate, builder: builder, nonce: nonce },
|
|
625
|
+
'HyperliquidTransaction:ApproveBuilderFee',
|
|
626
|
+
Signing::EIP712::APPROVE_BUILDER_FEE_TYPES
|
|
627
|
+
)
|
|
628
|
+
post_action(action, signature, nonce, nil)
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
# Delegate or undelegate HYPE tokens to a validator
|
|
632
|
+
# @param validator [String] Validator's Ethereum address
|
|
633
|
+
# @param wei [Integer] Amount as float * 1e8 (e.g., 1 HYPE = 100_000_000)
|
|
634
|
+
# @param is_undelegate [Boolean] True to undelegate, false to delegate
|
|
635
|
+
# @return [Hash] Token delegate response
|
|
636
|
+
def token_delegate(validator:, wei:, is_undelegate:)
|
|
637
|
+
nonce = timestamp_ms
|
|
638
|
+
action = {
|
|
639
|
+
type: 'tokenDelegate',
|
|
640
|
+
signatureChainId: '0x66eee',
|
|
641
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
642
|
+
validator: validator,
|
|
643
|
+
wei: wei,
|
|
644
|
+
isUndelegate: is_undelegate,
|
|
645
|
+
nonce: nonce
|
|
646
|
+
}
|
|
647
|
+
signature = @signer.sign_user_signed_action(
|
|
648
|
+
{ validator: validator, wei: wei, isUndelegate: is_undelegate, nonce: nonce },
|
|
649
|
+
'HyperliquidTransaction:TokenDelegate',
|
|
650
|
+
Signing::EIP712::TOKEN_DELEGATE_TYPES
|
|
651
|
+
)
|
|
652
|
+
post_action(action, signature, nonce, nil)
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
# Enable or disable HIP-3 DEX abstraction for automatic collateral transfers
|
|
656
|
+
# When enabled, collateral is automatically transferred to HIP-3 dexes when trading
|
|
657
|
+
# @param enabled [Boolean] True to enable, false to disable DEX abstraction
|
|
658
|
+
# @param user [String, nil] User address (defaults to signer address)
|
|
659
|
+
# @return [Hash] User DEX abstraction response
|
|
660
|
+
def user_dex_abstraction(enabled:, user: nil)
|
|
661
|
+
nonce = timestamp_ms
|
|
662
|
+
user_address = user || @signer.address
|
|
663
|
+
action = {
|
|
664
|
+
type: 'userDexAbstraction',
|
|
665
|
+
signatureChainId: '0x66eee',
|
|
666
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
667
|
+
user: user_address,
|
|
668
|
+
enabled: enabled,
|
|
669
|
+
nonce: nonce
|
|
670
|
+
}
|
|
671
|
+
signature = @signer.sign_user_signed_action(
|
|
672
|
+
{ user: user_address, enabled: enabled, nonce: nonce },
|
|
673
|
+
'HyperliquidTransaction:UserDexAbstraction',
|
|
674
|
+
Signing::EIP712::USER_DEX_ABSTRACTION_TYPES
|
|
675
|
+
)
|
|
676
|
+
post_action(action, signature, nonce, nil)
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
# Enable HIP-3 DEX abstraction via agent (L1 action, enable only)
|
|
680
|
+
# This allows agents to enable DEX abstraction for the account they're trading on behalf of
|
|
681
|
+
# @param vault_address [String, nil] Vault address if trading on behalf of a vault
|
|
682
|
+
# @return [Hash] Agent enable DEX abstraction response
|
|
683
|
+
def agent_enable_dex_abstraction(vault_address: nil)
|
|
684
|
+
nonce = timestamp_ms
|
|
685
|
+
action = { type: 'agentEnableDexAbstraction' }
|
|
686
|
+
signature = @signer.sign_l1_action(
|
|
687
|
+
action, nonce,
|
|
688
|
+
vault_address: vault_address,
|
|
689
|
+
expires_after: @expires_after
|
|
690
|
+
)
|
|
691
|
+
post_action(action, signature, nonce, vault_address)
|
|
692
|
+
end
|
|
693
|
+
|
|
572
694
|
# Clear the asset metadata cache
|
|
573
695
|
# Call this if metadata has been updated
|
|
574
696
|
def reload_metadata!
|
|
@@ -597,26 +719,54 @@ module Hyperliquid
|
|
|
597
719
|
end
|
|
598
720
|
|
|
599
721
|
# Get asset index for a coin symbol
|
|
600
|
-
# @param coin [String] Asset symbol
|
|
722
|
+
# @param coin [String] Asset symbol (supports HIP-3 prefixed names like "xyz:GOLD")
|
|
601
723
|
# @return [Integer] Asset index
|
|
602
724
|
def asset_index(coin)
|
|
603
725
|
load_asset_cache unless @asset_cache
|
|
726
|
+
|
|
727
|
+
# If not found and has a dex prefix (e.g., "xyz:GOLD"), try loading that dex
|
|
728
|
+
unless @asset_cache[:indices][coin]
|
|
729
|
+
dex_prefix = extract_dex_prefix(coin)
|
|
730
|
+
load_hip3_dex_cache(dex_prefix) if dex_prefix && !@loaded_dexes&.include?(dex_prefix)
|
|
731
|
+
end
|
|
732
|
+
|
|
604
733
|
@asset_cache[:indices][coin] || raise(ArgumentError, "Unknown asset: #{coin}")
|
|
605
734
|
end
|
|
606
735
|
|
|
607
736
|
# Get asset metadata for a coin symbol
|
|
608
|
-
# @param coin [String] Asset symbol
|
|
737
|
+
# @param coin [String] Asset symbol (supports HIP-3 prefixed names like "xyz:GOLD")
|
|
609
738
|
# @return [Hash] Asset metadata with :sz_decimals and :is_spot
|
|
610
739
|
def asset_metadata(coin)
|
|
611
740
|
load_asset_cache unless @asset_cache
|
|
741
|
+
|
|
742
|
+
# If not found and has a dex prefix, try loading that dex
|
|
743
|
+
unless @asset_cache[:metadata][coin]
|
|
744
|
+
dex_prefix = extract_dex_prefix(coin)
|
|
745
|
+
load_hip3_dex_cache(dex_prefix) if dex_prefix && !@loaded_dexes&.include?(dex_prefix)
|
|
746
|
+
end
|
|
747
|
+
|
|
612
748
|
@asset_cache[:metadata][coin] || raise(ArgumentError, "Unknown asset: #{coin}")
|
|
613
749
|
end
|
|
614
750
|
|
|
751
|
+
# Extract dex prefix from a coin name (e.g., "xyz:GOLD" -> "xyz")
|
|
752
|
+
# @param coin [String] Coin name
|
|
753
|
+
# @return [String, nil] Dex prefix or nil if no prefix
|
|
754
|
+
def extract_dex_prefix(coin)
|
|
755
|
+
return nil unless coin.include?(':')
|
|
756
|
+
|
|
757
|
+
prefix = coin.split(':').first
|
|
758
|
+
# Spot pairs like "PURR/USDC" don't count as dex prefixes
|
|
759
|
+
return nil if prefix.include?('/')
|
|
760
|
+
|
|
761
|
+
prefix
|
|
762
|
+
end
|
|
763
|
+
|
|
615
764
|
# Load asset metadata from Info API (perps and spot)
|
|
616
765
|
def load_asset_cache
|
|
617
766
|
@asset_cache = { indices: {}, metadata: {} }
|
|
767
|
+
@loaded_dexes = Set.new
|
|
618
768
|
|
|
619
|
-
# Load perpetual assets
|
|
769
|
+
# Load perpetual assets from default dex
|
|
620
770
|
meta = @info.meta
|
|
621
771
|
meta['universe'].each_with_index do |asset, index|
|
|
622
772
|
name = asset['name']
|
|
@@ -640,6 +790,33 @@ module Hyperliquid
|
|
|
640
790
|
end
|
|
641
791
|
end
|
|
642
792
|
|
|
793
|
+
# Load asset metadata for a HIP-3 dex
|
|
794
|
+
# HIP-3 asset IDs use formula: 100000 + perp_dex_index * 10000 + index_in_meta
|
|
795
|
+
# @param dex [String] Dex name (e.g., "xyz")
|
|
796
|
+
def load_hip3_dex_cache(dex)
|
|
797
|
+
return if @loaded_dexes.include?(dex)
|
|
798
|
+
|
|
799
|
+
@loaded_dexes.add(dex)
|
|
800
|
+
|
|
801
|
+
# Get perp_dex_index from perp_dexs list (position in array)
|
|
802
|
+
perp_dexs = @info.perp_dexs
|
|
803
|
+
perp_dex_index = perp_dexs.index { |d| d && d['name'] == dex }
|
|
804
|
+
return unless perp_dex_index
|
|
805
|
+
|
|
806
|
+
meta = @info.meta(dex: dex)
|
|
807
|
+
meta['universe']&.each_with_index do |asset, index|
|
|
808
|
+
name = asset['name']
|
|
809
|
+
# HIP-3 asset ID: 100000 + perp_dex_index * 10000 + index_in_meta
|
|
810
|
+
hip3_asset_id = 100_000 + (perp_dex_index * 10_000) + index
|
|
811
|
+
@asset_cache[:indices][name] = hip3_asset_id
|
|
812
|
+
@asset_cache[:metadata][name] = {
|
|
813
|
+
sz_decimals: asset['szDecimals'],
|
|
814
|
+
is_spot: false,
|
|
815
|
+
dex: dex
|
|
816
|
+
}
|
|
817
|
+
end
|
|
818
|
+
end
|
|
819
|
+
|
|
643
820
|
# Convert float to wire format (string representation)
|
|
644
821
|
# Maintains parity with official Python SDK
|
|
645
822
|
# - 8 decimal precision
|
|
@@ -745,6 +922,13 @@ module Hyperliquid
|
|
|
745
922
|
end
|
|
746
923
|
end
|
|
747
924
|
|
|
925
|
+
# Normalize builder fee config for inclusion in action payload
|
|
926
|
+
# @param builder [Hash] Builder config with :b (address) and :f (fee)
|
|
927
|
+
# @return [Hash] Normalized builder config with lowercased address
|
|
928
|
+
def normalize_builder(builder)
|
|
929
|
+
{ b: builder[:b].downcase, f: builder[:f] }
|
|
930
|
+
end
|
|
931
|
+
|
|
748
932
|
# Convert order type to wire format
|
|
749
933
|
# @param order_type [Hash] Order type configuration
|
|
750
934
|
# @return [Hash] Wire format order type
|
data/lib/hyperliquid/info.rb
CHANGED
|
@@ -12,9 +12,12 @@ module Hyperliquid
|
|
|
12
12
|
# ============================
|
|
13
13
|
|
|
14
14
|
# Get all market mid prices
|
|
15
|
+
# @param dex [String, nil] Optional perp dex name (defaults to first perp dex; spot mids only included with first perp dex)
|
|
15
16
|
# @return [Hash] Hash containing mid prices for all markets
|
|
16
|
-
def all_mids
|
|
17
|
-
|
|
17
|
+
def all_mids(dex: nil)
|
|
18
|
+
body = { type: 'allMids' }
|
|
19
|
+
body[:dex] = dex if dex
|
|
20
|
+
@client.post(Constants::INFO_ENDPOINT, body)
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
# Get a user's open orders
|
|
@@ -215,6 +218,27 @@ module Hyperliquid
|
|
|
215
218
|
@client.post(Constants::INFO_ENDPOINT, { type: 'delegatorRewards', user: user })
|
|
216
219
|
end
|
|
217
220
|
|
|
221
|
+
# Get authorized agent addresses for a user
|
|
222
|
+
# @param user [String] Wallet address
|
|
223
|
+
# @return [Array] Array of authorized agent addresses
|
|
224
|
+
def extra_agents(user)
|
|
225
|
+
@client.post(Constants::INFO_ENDPOINT, { type: 'extraAgents', user: user })
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Get multi-sig signer mappings for a user
|
|
229
|
+
# @param user [String] Multi-sig wallet address
|
|
230
|
+
# @return [Hash] Multi-sig signer information
|
|
231
|
+
def user_to_multi_sig_signers(user)
|
|
232
|
+
@client.post(Constants::INFO_ENDPOINT, { type: 'userToMultiSigSigners', user: user })
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Get dex abstraction config for a user
|
|
236
|
+
# @param user [String] Wallet address
|
|
237
|
+
# @return [Hash] Dex abstraction configuration
|
|
238
|
+
def user_dex_abstraction(user)
|
|
239
|
+
@client.post(Constants::INFO_ENDPOINT, { type: 'userDexAbstraction', user: user })
|
|
240
|
+
end
|
|
241
|
+
|
|
218
242
|
# ============================
|
|
219
243
|
# Info: Perpetuals
|
|
220
244
|
# ============================
|
|
@@ -67,6 +67,43 @@ module Hyperliquid
|
|
|
67
67
|
]
|
|
68
68
|
}.freeze
|
|
69
69
|
|
|
70
|
+
APPROVE_AGENT_TYPES = {
|
|
71
|
+
'HyperliquidTransaction:ApproveAgent': [
|
|
72
|
+
{ name: :hyperliquidChain, type: 'string' },
|
|
73
|
+
{ name: :agentAddress, type: 'address' },
|
|
74
|
+
{ name: :agentName, type: 'string' },
|
|
75
|
+
{ name: :nonce, type: 'uint64' }
|
|
76
|
+
]
|
|
77
|
+
}.freeze
|
|
78
|
+
|
|
79
|
+
APPROVE_BUILDER_FEE_TYPES = {
|
|
80
|
+
'HyperliquidTransaction:ApproveBuilderFee': [
|
|
81
|
+
{ name: :hyperliquidChain, type: 'string' },
|
|
82
|
+
{ name: :maxFeeRate, type: 'string' },
|
|
83
|
+
{ name: :builder, type: 'address' },
|
|
84
|
+
{ name: :nonce, type: 'uint64' }
|
|
85
|
+
]
|
|
86
|
+
}.freeze
|
|
87
|
+
|
|
88
|
+
TOKEN_DELEGATE_TYPES = {
|
|
89
|
+
'HyperliquidTransaction:TokenDelegate': [
|
|
90
|
+
{ name: :hyperliquidChain, type: 'string' },
|
|
91
|
+
{ name: :validator, type: 'address' },
|
|
92
|
+
{ name: :wei, type: 'uint64' },
|
|
93
|
+
{ name: :isUndelegate, type: 'bool' },
|
|
94
|
+
{ name: :nonce, type: 'uint64' }
|
|
95
|
+
]
|
|
96
|
+
}.freeze
|
|
97
|
+
|
|
98
|
+
USER_DEX_ABSTRACTION_TYPES = {
|
|
99
|
+
'HyperliquidTransaction:UserDexAbstraction': [
|
|
100
|
+
{ name: :hyperliquidChain, type: 'string' },
|
|
101
|
+
{ name: :user, type: 'address' },
|
|
102
|
+
{ name: :enabled, type: 'bool' },
|
|
103
|
+
{ name: :nonce, type: 'uint64' }
|
|
104
|
+
]
|
|
105
|
+
}.freeze
|
|
106
|
+
|
|
70
107
|
class << self
|
|
71
108
|
# Domain for L1 actions (orders, cancels, leverage, etc.)
|
|
72
109
|
# @return [Hash] EIP-712 domain configuration
|
|
@@ -22,6 +22,12 @@ module Hyperliquid
|
|
|
22
22
|
@key.address.to_s
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# Whether the signer targets testnet
|
|
26
|
+
# @return [Boolean]
|
|
27
|
+
def testnet?
|
|
28
|
+
@testnet
|
|
29
|
+
end
|
|
30
|
+
|
|
25
31
|
# Sign an L1 action (orders, cancels, leverage updates, etc.)
|
|
26
32
|
# @param action [Hash] The action payload to sign
|
|
27
33
|
# @param nonce [Integer] Timestamp in milliseconds
|
data/lib/hyperliquid/version.rb
CHANGED