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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 324ff762cbf5edcea7fc532f853b7295ffcd34476f6273fdef4e03bcc0f9eaee
4
- data.tar.gz: 98b1b88b378bb8ae010301c656c833282cd406822fa0c8a7d6c134911b0f2cce
3
+ metadata.gz: 67b19ec4599a62be35288947fdc8ce2a98c6a0f012f4bdb055a58bd9e4a37849
4
+ data.tar.gz: 58470f99ebf59a1ba95936816c887210d5937240c55ea1b6e85f7d68b0d82a37
5
5
  SHA512:
6
- metadata.gz: fe9e447e5500312f98e8f8e602071372847963daacfd51475e92d4db5a7bc988546e4cf106479a9c899bcecf113612c2aa9ebe0dd50a5384bd438bb0d6429865
7
- data.tar.gz: 7cd7d2e048c4bf25e128dd7fd41814621bd95305d613199cb4c41abdf1901fdc4c665635000112aafeb7c395dbf01dae81cba74042557d86643c6ac169e3d201
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
@@ -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: @signer.instance_variable_get(:@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: @signer.instance_variable_get(:@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: @signer.instance_variable_get(:@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: @signer.instance_variable_get(:@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: @signer.instance_variable_get(:@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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hyperliquid
4
- VERSION = '0.6.0'
4
+ VERSION = '0.7.0'
5
5
  end
data/lib/hyperliquid.rb CHANGED
@@ -58,6 +58,7 @@ module Hyperliquid
58
58
  client: client,
59
59
  signer: signer,
60
60
  info: @info,
61
+ testnet: testnet,
61
62
  expires_after: expires_after
62
63
  )
63
64
  end
@@ -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
- 'test_10_vault_transfer.rb'
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.6.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/test_10_vault_transfer.rb
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')