hyperliquid 0.6.0 → 0.7.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 +10 -0
- data/docs/API.md +8 -0
- data/docs/EXAMPLES.md +56 -0
- data/lib/hyperliquid/exchange.rb +100 -12
- data/lib/hyperliquid/signing/eip712.rb +28 -0
- data/lib/hyperliquid/signing/signer.rb +6 -0
- data/lib/hyperliquid/version.rb +1 -1
- data/lib/hyperliquid.rb +1 -0
- data/scripts/test_10_vault.rb +66 -0
- data/scripts/test_11_builder_fee.rb +70 -0
- data/scripts/test_12_staking.rb +68 -0
- data/scripts/test_all.rb +3 -1
- metadata +4 -2
- data/scripts/test_10_vault_transfer.rb +0 -41
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 67b19ec4599a62be35288947fdc8ce2a98c6a0f012f4bdb055a58bd9e4a37849
|
|
4
|
+
data.tar.gz: 58470f99ebf59a1ba95936816c887210d5937240c55ea1b6e85f7d68b0d82a37
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fe341138c6085166b2ddd6e87fd7458554e178c4361e9ad16ed7ab1d239333c821dc0556d7f86d9080ec2002c3c36f26b3f32282acc2b6ccc010b85f83c83abe
|
|
7
|
+
data.tar.gz: caaefe028d2a4727c01daeea7241290af00c8676d73c2371ce360279f4dc0c18337c1daa398ab968416ee2516f02d08882b0556d91b5e5e78d318bfbd2d7531f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## [Ruby Hyperliquid SDK Changelog]
|
|
2
2
|
|
|
3
|
+
## [0.7.0] - 2026-01-30
|
|
4
|
+
|
|
5
|
+
- Add agent, builder & delegation actions to Exchange API
|
|
6
|
+
- `approve_agent` — authorize an agent wallet to trade on behalf of the account
|
|
7
|
+
- `approve_builder_fee` — approve a builder fee rate for a builder address
|
|
8
|
+
- `token_delegate` — delegate or undelegate HYPE tokens to a validator
|
|
9
|
+
- Add builder fee support on order placement
|
|
10
|
+
- Optional `builder:` parameter on `order`, `bulk_orders`, `market_order`, `market_close`
|
|
11
|
+
- Add EIP-712 type definitions for `ApproveAgent`, `ApproveBuilderFee`, and `TokenDelegate`
|
|
12
|
+
|
|
3
13
|
## [0.6.0] - 2026-01-28
|
|
4
14
|
|
|
5
15
|
- Add transfers and account management to Exchange API
|
data/docs/API.md
CHANGED
|
@@ -97,12 +97,20 @@ Read-only methods for querying market data and user information.
|
|
|
97
97
|
- `vault_transfer(vault_address:, is_deposit:, usd:)` - Deposit or withdraw USDC to/from a vault
|
|
98
98
|
- `set_referrer(code:)` - Set referral code
|
|
99
99
|
|
|
100
|
+
### Agent & Builder
|
|
101
|
+
|
|
102
|
+
- `approve_agent(agent_address:, agent_name:)` - Authorize an agent wallet to trade on behalf of this account
|
|
103
|
+
- `approve_builder_fee(builder:, max_fee_rate:)` - Approve a builder fee rate for a builder address
|
|
104
|
+
- `token_delegate(validator:, wei:, is_undelegate:)` - Delegate or undelegate HYPE tokens to a validator
|
|
105
|
+
|
|
100
106
|
### Other
|
|
101
107
|
|
|
102
108
|
- `address` - Get the wallet address associated with the private key
|
|
103
109
|
|
|
104
110
|
Order placement and management methods support an optional `vault_address:` parameter for vault trading.
|
|
105
111
|
|
|
112
|
+
Order placement methods (`order`, `bulk_orders`, `market_order`, `market_close`) support an optional `builder:` parameter for builder fee integration. The builder is a Hash with `:b` (builder address) and `:f` (fee in tenths of a basis point).
|
|
113
|
+
|
|
106
114
|
### Order Types
|
|
107
115
|
|
|
108
116
|
- `{ limit: { tif: 'Gtc' } }` - Good-til-canceled (default)
|
data/docs/EXAMPLES.md
CHANGED
|
@@ -479,6 +479,62 @@ sdk.exchange.vault_transfer(
|
|
|
479
479
|
sdk.exchange.set_referrer(code: 'MY_REFERRAL_CODE')
|
|
480
480
|
```
|
|
481
481
|
|
|
482
|
+
### Agent, Builder & Delegation
|
|
483
|
+
|
|
484
|
+
```ruby
|
|
485
|
+
# Authorize an agent wallet to trade on your behalf
|
|
486
|
+
agent_key = Eth::Key.new
|
|
487
|
+
result = sdk.exchange.approve_agent(
|
|
488
|
+
agent_address: agent_key.address.to_s,
|
|
489
|
+
agent_name: 'my-trading-bot' # optional
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
# Approve a builder fee (required before placing orders with that builder)
|
|
493
|
+
sdk.exchange.approve_builder_fee(
|
|
494
|
+
builder: '0x250F311Ae04D3CEA03443C76340069eD26C47D7D',
|
|
495
|
+
max_fee_rate: '0.01%' # 1 basis point
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
# Place an order with a builder fee
|
|
499
|
+
sdk.exchange.order(
|
|
500
|
+
coin: 'BTC',
|
|
501
|
+
is_buy: true,
|
|
502
|
+
size: '0.01',
|
|
503
|
+
limit_px: '95000',
|
|
504
|
+
builder: { b: '0x250F311Ae04D3CEA03443C76340069eD26C47D7D', f: 10 } # f=10 means 1bp
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
# Builder works with all order methods
|
|
508
|
+
sdk.exchange.bulk_orders(
|
|
509
|
+
orders: [
|
|
510
|
+
{ coin: 'BTC', is_buy: true, size: '0.01', limit_px: '94000' },
|
|
511
|
+
{ coin: 'BTC', is_buy: false, size: '0.01', limit_px: '96000' }
|
|
512
|
+
],
|
|
513
|
+
builder: { b: '0x250F311Ae04D3CEA03443C76340069eD26C47D7D', f: 10 }
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
sdk.exchange.market_order(
|
|
517
|
+
coin: 'BTC',
|
|
518
|
+
is_buy: true,
|
|
519
|
+
size: '0.01',
|
|
520
|
+
builder: { b: '0x250F311Ae04D3CEA03443C76340069eD26C47D7D', f: 10 }
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
# Delegate HYPE tokens to a validator (wei = float * 1e8)
|
|
524
|
+
sdk.exchange.token_delegate(
|
|
525
|
+
validator: '0x...',
|
|
526
|
+
wei: 100_000_000, # 1 HYPE
|
|
527
|
+
is_undelegate: false
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
# Undelegate HYPE tokens
|
|
531
|
+
sdk.exchange.token_delegate(
|
|
532
|
+
validator: '0x...',
|
|
533
|
+
wei: 10_000_000, # 0.1 HYPE
|
|
534
|
+
is_undelegate: true
|
|
535
|
+
)
|
|
536
|
+
```
|
|
537
|
+
|
|
482
538
|
### Client Order IDs (Cloid)
|
|
483
539
|
|
|
484
540
|
```ruby
|
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,8 +116,9 @@ 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)
|
|
121
|
+
def market_order(coin:, is_buy:, size:, slippage: DEFAULT_SLIPPAGE, vault_address: nil, builder: nil)
|
|
115
122
|
# Get current mid price
|
|
116
123
|
mids = @info.all_mids
|
|
117
124
|
mid = mids[coin]&.to_f
|
|
@@ -126,7 +133,8 @@ module Hyperliquid
|
|
|
126
133
|
size: size,
|
|
127
134
|
limit_px: slippage_price,
|
|
128
135
|
order_type: { limit: { tif: 'Ioc' } },
|
|
129
|
-
vault_address: vault_address
|
|
136
|
+
vault_address: vault_address,
|
|
137
|
+
builder: builder
|
|
130
138
|
)
|
|
131
139
|
end
|
|
132
140
|
|
|
@@ -342,8 +350,9 @@ module Hyperliquid
|
|
|
342
350
|
# @param slippage [Float] Slippage tolerance (default: 5%)
|
|
343
351
|
# @param cloid [Cloid, String, nil] Client order ID (optional)
|
|
344
352
|
# @param vault_address [String, nil] Vault address for vault trading (optional)
|
|
353
|
+
# @param builder [Hash, nil] Builder fee config { b: "0xaddress", f: fee_in_tenths_of_bp } (optional)
|
|
345
354
|
# @return [Hash] Order response
|
|
346
|
-
def market_close(coin:, size: nil, slippage: DEFAULT_SLIPPAGE, cloid: nil, vault_address: nil)
|
|
355
|
+
def market_close(coin:, size: nil, slippage: DEFAULT_SLIPPAGE, cloid: nil, vault_address: nil, builder: nil)
|
|
347
356
|
address = vault_address || @signer.address
|
|
348
357
|
state = @info.user_state(address)
|
|
349
358
|
|
|
@@ -370,7 +379,8 @@ module Hyperliquid
|
|
|
370
379
|
order_type: { limit: { tif: 'Ioc' } },
|
|
371
380
|
reduce_only: true,
|
|
372
381
|
cloid: cloid,
|
|
373
|
-
vault_address: vault_address
|
|
382
|
+
vault_address: vault_address,
|
|
383
|
+
builder: builder
|
|
374
384
|
)
|
|
375
385
|
end
|
|
376
386
|
|
|
@@ -383,7 +393,7 @@ module Hyperliquid
|
|
|
383
393
|
action = {
|
|
384
394
|
type: 'usdSend',
|
|
385
395
|
signatureChainId: '0x66eee',
|
|
386
|
-
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @
|
|
396
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
387
397
|
destination: destination,
|
|
388
398
|
amount: amount.to_s,
|
|
389
399
|
time: nonce
|
|
@@ -406,7 +416,7 @@ module Hyperliquid
|
|
|
406
416
|
action = {
|
|
407
417
|
type: 'spotSend',
|
|
408
418
|
signatureChainId: '0x66eee',
|
|
409
|
-
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @
|
|
419
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
410
420
|
destination: destination,
|
|
411
421
|
token: token,
|
|
412
422
|
amount: amount.to_s,
|
|
@@ -429,7 +439,7 @@ module Hyperliquid
|
|
|
429
439
|
action = {
|
|
430
440
|
type: 'usdClassTransfer',
|
|
431
441
|
signatureChainId: '0x66eee',
|
|
432
|
-
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @
|
|
442
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
433
443
|
amount: amount.to_s,
|
|
434
444
|
toPerp: to_perp,
|
|
435
445
|
nonce: nonce
|
|
@@ -451,7 +461,7 @@ module Hyperliquid
|
|
|
451
461
|
action = {
|
|
452
462
|
type: 'withdraw3',
|
|
453
463
|
signatureChainId: '0x66eee',
|
|
454
|
-
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @
|
|
464
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
455
465
|
destination: destination,
|
|
456
466
|
amount: amount.to_s,
|
|
457
467
|
time: nonce
|
|
@@ -476,7 +486,7 @@ module Hyperliquid
|
|
|
476
486
|
action = {
|
|
477
487
|
type: 'sendAsset',
|
|
478
488
|
signatureChainId: '0x66eee',
|
|
479
|
-
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @
|
|
489
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
480
490
|
destination: destination,
|
|
481
491
|
sourceDex: source_dex,
|
|
482
492
|
destinationDex: destination_dex,
|
|
@@ -569,6 +579,77 @@ module Hyperliquid
|
|
|
569
579
|
post_action(action, signature, nonce, nil)
|
|
570
580
|
end
|
|
571
581
|
|
|
582
|
+
# Authorize an agent wallet to trade on behalf of this account
|
|
583
|
+
# @param agent_address [String] Agent's Ethereum address
|
|
584
|
+
# @param agent_name [String, nil] Optional agent name (omitted from action if nil)
|
|
585
|
+
# @return [Hash] Approve agent response
|
|
586
|
+
def approve_agent(agent_address:, agent_name: nil)
|
|
587
|
+
nonce = timestamp_ms
|
|
588
|
+
action = {
|
|
589
|
+
type: 'approveAgent',
|
|
590
|
+
signatureChainId: '0x66eee',
|
|
591
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
592
|
+
agentAddress: agent_address,
|
|
593
|
+
nonce: nonce
|
|
594
|
+
}
|
|
595
|
+
# agentName is always included in the signed message (empty string if nil),
|
|
596
|
+
# but only included in the posted action if a name was provided (matches Python SDK)
|
|
597
|
+
action[:agentName] = agent_name if agent_name
|
|
598
|
+
signature = @signer.sign_user_signed_action(
|
|
599
|
+
{ agentAddress: agent_address, agentName: agent_name || '', nonce: nonce },
|
|
600
|
+
'HyperliquidTransaction:ApproveAgent',
|
|
601
|
+
Signing::EIP712::APPROVE_AGENT_TYPES
|
|
602
|
+
)
|
|
603
|
+
post_action(action, signature, nonce, nil)
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
# Approve a builder fee rate for a builder address
|
|
607
|
+
# Users must approve a builder before orders with that builder can be placed.
|
|
608
|
+
# @param builder [String] Builder's Ethereum address
|
|
609
|
+
# @param max_fee_rate [String] Maximum fee rate (e.g., "0.01%" for 1 basis point)
|
|
610
|
+
# @return [Hash] Approve builder fee response
|
|
611
|
+
def approve_builder_fee(builder:, max_fee_rate:)
|
|
612
|
+
nonce = timestamp_ms
|
|
613
|
+
action = {
|
|
614
|
+
type: 'approveBuilderFee',
|
|
615
|
+
signatureChainId: '0x66eee',
|
|
616
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
617
|
+
maxFeeRate: max_fee_rate,
|
|
618
|
+
builder: builder,
|
|
619
|
+
nonce: nonce
|
|
620
|
+
}
|
|
621
|
+
signature = @signer.sign_user_signed_action(
|
|
622
|
+
{ maxFeeRate: max_fee_rate, builder: builder, nonce: nonce },
|
|
623
|
+
'HyperliquidTransaction:ApproveBuilderFee',
|
|
624
|
+
Signing::EIP712::APPROVE_BUILDER_FEE_TYPES
|
|
625
|
+
)
|
|
626
|
+
post_action(action, signature, nonce, nil)
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
# Delegate or undelegate HYPE tokens to a validator
|
|
630
|
+
# @param validator [String] Validator's Ethereum address
|
|
631
|
+
# @param wei [Integer] Amount as float * 1e8 (e.g., 1 HYPE = 100_000_000)
|
|
632
|
+
# @param is_undelegate [Boolean] True to undelegate, false to delegate
|
|
633
|
+
# @return [Hash] Token delegate response
|
|
634
|
+
def token_delegate(validator:, wei:, is_undelegate:)
|
|
635
|
+
nonce = timestamp_ms
|
|
636
|
+
action = {
|
|
637
|
+
type: 'tokenDelegate',
|
|
638
|
+
signatureChainId: '0x66eee',
|
|
639
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
640
|
+
validator: validator,
|
|
641
|
+
wei: wei,
|
|
642
|
+
isUndelegate: is_undelegate,
|
|
643
|
+
nonce: nonce
|
|
644
|
+
}
|
|
645
|
+
signature = @signer.sign_user_signed_action(
|
|
646
|
+
{ validator: validator, wei: wei, isUndelegate: is_undelegate, nonce: nonce },
|
|
647
|
+
'HyperliquidTransaction:TokenDelegate',
|
|
648
|
+
Signing::EIP712::TOKEN_DELEGATE_TYPES
|
|
649
|
+
)
|
|
650
|
+
post_action(action, signature, nonce, nil)
|
|
651
|
+
end
|
|
652
|
+
|
|
572
653
|
# Clear the asset metadata cache
|
|
573
654
|
# Call this if metadata has been updated
|
|
574
655
|
def reload_metadata!
|
|
@@ -745,6 +826,13 @@ module Hyperliquid
|
|
|
745
826
|
end
|
|
746
827
|
end
|
|
747
828
|
|
|
829
|
+
# Normalize builder fee config for inclusion in action payload
|
|
830
|
+
# @param builder [Hash] Builder config with :b (address) and :f (fee)
|
|
831
|
+
# @return [Hash] Normalized builder config with lowercased address
|
|
832
|
+
def normalize_builder(builder)
|
|
833
|
+
{ b: builder[:b].downcase, f: builder[:f] }
|
|
834
|
+
end
|
|
835
|
+
|
|
748
836
|
# Convert order type to wire format
|
|
749
837
|
# @param order_type [Hash] Order type configuration
|
|
750
838
|
# @return [Hash] Wire format order type
|
|
@@ -67,6 +67,34 @@ 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
|
+
|
|
70
98
|
class << self
|
|
71
99
|
# Domain for L1 actions (orders, cancels, leverage, etc.)
|
|
72
100
|
# @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
data/lib/hyperliquid.rb
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
api_error?(result) || puts(green('Vault deposit successful!'))
|
|
46
|
+
when 'withdraw'
|
|
47
|
+
equity_f = follower&.dig('vaultEquity')&.to_f || 0
|
|
48
|
+
if equity_f > 1
|
|
49
|
+
withdraw_amount = equity_f.floor
|
|
50
|
+
puts "Withdrawing $#{withdraw_amount} from vault..."
|
|
51
|
+
result = sdk.exchange.vault_transfer(
|
|
52
|
+
vault_address: vault_addr,
|
|
53
|
+
is_deposit: false,
|
|
54
|
+
usd: withdraw_amount
|
|
55
|
+
)
|
|
56
|
+
api_error?(result) || puts(green('Vault withdrawal successful!'))
|
|
57
|
+
else
|
|
58
|
+
puts red("Insufficient vault equity to withdraw ($#{equity_f})")
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
puts 'Pass "deposit" or "withdraw" as an argument to perform a transfer.'
|
|
62
|
+
puts ' ruby scripts/test_10_vault_transfer.rb deposit'
|
|
63
|
+
puts ' ruby scripts/test_10_vault_transfer.rb withdraw'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
test_passed('Test 10 Vault Status')
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
puts "Approving builder fee for #{builder_address} (max #{max_fee_rate})..."
|
|
18
|
+
result = sdk.exchange.approve_builder_fee(builder: builder_address, max_fee_rate: max_fee_rate)
|
|
19
|
+
api_error?(result) || puts(green('Builder fee approved'))
|
|
20
|
+
puts
|
|
21
|
+
|
|
22
|
+
wait_with_countdown(WAIT_SECONDS, 'Waiting before placing order with builder...')
|
|
23
|
+
|
|
24
|
+
# Step 2: Verify approval via Info API
|
|
25
|
+
puts 'Checking builder fee approval...'
|
|
26
|
+
approval = sdk.info.max_builder_fee(sdk.exchange.address, builder_address)
|
|
27
|
+
puts "Max builder fee: #{approval.inspect}"
|
|
28
|
+
puts
|
|
29
|
+
|
|
30
|
+
# Step 3: Place order with builder param
|
|
31
|
+
mids = sdk.info.all_mids
|
|
32
|
+
btc_price = mids[perp_coin]&.to_f
|
|
33
|
+
|
|
34
|
+
if btc_price&.positive?
|
|
35
|
+
meta = sdk.info.meta
|
|
36
|
+
btc_meta = meta['universe'].find { |a| a['name'] == perp_coin }
|
|
37
|
+
sz_decimals = btc_meta['szDecimals']
|
|
38
|
+
|
|
39
|
+
limit_price = (btc_price * 1.50).round(0).to_i
|
|
40
|
+
perp_size = (20.0 / btc_price).ceil(sz_decimals)
|
|
41
|
+
|
|
42
|
+
puts "#{perp_coin} mid: $#{btc_price.round(2)}"
|
|
43
|
+
puts "Limit price: $#{limit_price} (50% above mid - won't fill)"
|
|
44
|
+
puts "Size: #{perp_size} BTC"
|
|
45
|
+
puts "Builder: #{builder_address} (fee: 10 = 1bp)"
|
|
46
|
+
puts
|
|
47
|
+
|
|
48
|
+
puts 'Placing limit SELL order with builder fee...'
|
|
49
|
+
result = sdk.exchange.order(
|
|
50
|
+
coin: perp_coin,
|
|
51
|
+
is_buy: false,
|
|
52
|
+
size: perp_size,
|
|
53
|
+
limit_px: limit_price,
|
|
54
|
+
order_type: { limit: { tif: 'Gtc' } },
|
|
55
|
+
builder: { b: builder_address, f: 10 }
|
|
56
|
+
)
|
|
57
|
+
oid = check_result(result, 'Limit short with builder')
|
|
58
|
+
|
|
59
|
+
if oid.is_a?(Integer)
|
|
60
|
+
wait_with_countdown(WAIT_SECONDS, 'Order resting. Waiting before cancel...')
|
|
61
|
+
|
|
62
|
+
puts "Canceling order #{oid}..."
|
|
63
|
+
result = sdk.exchange.cancel(coin: perp_coin, oid: oid)
|
|
64
|
+
check_result(result, 'Cancel')
|
|
65
|
+
end
|
|
66
|
+
else
|
|
67
|
+
puts red("SKIPPED: Could not get #{perp_coin} price")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
test_passed('Test 11 Builder Fee')
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
api_error?(result) || puts(green('Delegation successful!'))
|
|
54
|
+
when 'undelegate'
|
|
55
|
+
puts "Undelegating 0.1 HYPE from #{validator}..."
|
|
56
|
+
result = sdk.exchange.token_delegate(
|
|
57
|
+
validator: validator,
|
|
58
|
+
wei: delegate_amount,
|
|
59
|
+
is_undelegate: true
|
|
60
|
+
)
|
|
61
|
+
api_error?(result) || puts(green('Undelegation successful!'))
|
|
62
|
+
else
|
|
63
|
+
puts 'Pass "delegate" or "undelegate" as an argument to perform an action.'
|
|
64
|
+
puts ' ruby scripts/test_12_staking.rb delegate'
|
|
65
|
+
puts ' ruby scripts/test_12_staking.rb undelegate'
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
test_passed('Test 12 Staking')
|
data/scripts/test_all.rb
CHANGED
|
@@ -28,7 +28,9 @@ SCRIPTS = [
|
|
|
28
28
|
'test_07_market_close.rb',
|
|
29
29
|
'test_08_usd_class_transfer.rb',
|
|
30
30
|
'test_09_sub_account_lifecycle.rb',
|
|
31
|
-
'
|
|
31
|
+
'test_10_vault.rb',
|
|
32
|
+
'test_11_builder_fee.rb',
|
|
33
|
+
'test_12_staking.rb'
|
|
32
34
|
].freeze
|
|
33
35
|
|
|
34
36
|
def green(text)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hyperliquid
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- carter2099
|
|
@@ -108,7 +108,9 @@ files:
|
|
|
108
108
|
- scripts/test_07_market_close.rb
|
|
109
109
|
- scripts/test_08_usd_class_transfer.rb
|
|
110
110
|
- scripts/test_09_sub_account_lifecycle.rb
|
|
111
|
-
- scripts/
|
|
111
|
+
- scripts/test_10_vault.rb
|
|
112
|
+
- scripts/test_11_builder_fee.rb
|
|
113
|
+
- scripts/test_12_staking.rb
|
|
112
114
|
- scripts/test_all.rb
|
|
113
115
|
- scripts/test_helpers.rb
|
|
114
116
|
- sig/hyperliquid.rbs
|
|
@@ -1,41 +0,0 @@
|
|
|
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')
|