hyperliquid 1.4.0 → 1.5.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 +16 -0
- data/CLAUDE.md +2 -0
- data/lib/hyperliquid/exchange.rb +57 -0
- data/lib/hyperliquid/signing/eip712.rb +8 -0
- data/lib/hyperliquid/signing/multi_sig.rb +111 -0
- data/lib/hyperliquid/signing/signer.rb +30 -37
- data/lib/hyperliquid/version.rb +1 -1
- data/lib/hyperliquid.rb +1 -0
- data/scripts/test_17_create_vault.rb +73 -0
- data/scripts/test_all.rb +2 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3bf3db383f0da2f70b1d7e98e8901f29fbb4cde940a18859c57c98592c4ffbf3
|
|
4
|
+
data.tar.gz: 0055af5f445d241193e27189a585423e3410d296a7df96ba447827299a9b9cd4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 32986974696870940a6ca517f963be23faf84c3dfc7adbfe14f0bcfb5d919118c683ab7f3faa9fd3cfa3249c95c92d89f240cc15917e3e18092331e6141a59de
|
|
7
|
+
data.tar.gz: 9a2d495448ba88243b14bfae7f0fe81ddebab3dbc743dce21742b58955196ff9cdd7a6e6a45cea2d958e00cd023748250c923d203258e32d346390638dd4e9de
|
data/.rubocop.yml
CHANGED
|
@@ -26,10 +26,11 @@ Metrics/BlockLength:
|
|
|
26
26
|
- 'spec/**/*'
|
|
27
27
|
- '*.gemspec'
|
|
28
28
|
|
|
29
|
-
# Exchange API methods require many parameters
|
|
29
|
+
# Exchange API methods (and multi-sig co-signer helpers that pass through to them) require many parameters
|
|
30
30
|
Metrics/ParameterLists:
|
|
31
31
|
Exclude:
|
|
32
32
|
- 'lib/hyperliquid/exchange.rb'
|
|
33
|
+
- 'lib/hyperliquid/signing/multi_sig.rb'
|
|
33
34
|
|
|
34
35
|
# Allow higher complexity for order type conversion logic and WS message handling
|
|
35
36
|
Metrics/CyclomaticComplexity:
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
## [Ruby Hyperliquid SDK Changelog]
|
|
2
2
|
|
|
3
|
+
## [1.5.0] - 2026-05-08
|
|
4
|
+
|
|
5
|
+
### New Exchange actions
|
|
6
|
+
|
|
7
|
+
- `Exchange#multi_sig(multi_sig_user:, inner_action:, signatures:, nonce:, vault_address:)` — wraps any inner action with N pre-collected co-signer signatures and submits via the user-signed multi-sig envelope. Adds `MULTI_SIG_TYPES` (first EIP-712 type using `bytes32`).
|
|
8
|
+
- `Exchange#create_vault(name:, description:, initial_usd:, ...)` — L1 action that creates a new vault with the calling wallet as leader. Float-USD scaled to integer micro-USD via `float_to_usd_int`.
|
|
9
|
+
|
|
10
|
+
### New signing primitives
|
|
11
|
+
|
|
12
|
+
- `Signing::MultiSig` module with `build_envelope`, `envelope_action_hash`, `sign_as_co_signer_l1` (for L1 inner actions, phantom-agent flow), and `sign_as_co_signer_user_signed` (enriches the inner action's typed-data spec with `payloadMultiSigUser` + `outerSigner` fields). Co-signature collection remains the caller's responsibility.
|
|
13
|
+
- `Signer.compute_action_hash` exposed as a public class method (refactored out of `construct_phantom_agent` for reuse from multi-sig).
|
|
14
|
+
|
|
15
|
+
### Tests
|
|
16
|
+
|
|
17
|
+
- Fixture-based byte-parity specs against Python `eth_account` 0.13.7 + `msgpack` 1.1.2 covering: outer multi-sig signature with no co-signers, outer + L1 co-signer signatures over an order action, and a user-signed co-signer signature over a sendAsset action.
|
|
18
|
+
|
|
3
19
|
## [1.4.0] - 2026-05-05
|
|
4
20
|
|
|
5
21
|
### New Exchange actions
|
data/CLAUDE.md
CHANGED
|
@@ -65,6 +65,8 @@ Any change to signing must maintain parity with the Python SDK or transactions w
|
|
|
65
65
|
|
|
66
66
|
User-signed actions (`usd_send`, `withdraw_from_bridge`, `send_to_evm_with_data`, etc.) use direct EIP-712 typed-data signing with the `HyperliquidSignTransaction` domain (chain ID 421614) — not the phantom-agent flow. Each has a typed-data spec in `Signing::EIP712`. The `eth` gem's typed-data signer handles primitive types (`string`, `uint*`, `address`, `bool`) and dynamic `bytes` correctly — `send_to_evm_with_data` was the first to use `bytes`, and its spec includes a fixture-based signature parity test against `eth_account` to lock that in. When adding new user-signed actions with non-string types, add a similar fixture to catch eth-gem regressions.
|
|
67
67
|
|
|
68
|
+
Multi-sig actions (`Exchange#multi_sig`) wrap any inner action with N co-signer signatures. The submitter's outer signature uses `MULTI_SIG_TYPES` over `{hyperliquidChain, multiSigActionHash, nonce}`; the `multiSigActionHash` is `Signer.compute_action_hash` of the multi-sig envelope (with `:type` stripped). Co-signer signing is exposed via `Signing::MultiSig.sign_as_co_signer_l1` (for L1 inner actions — signs `[multi_sig_user, outer_signer, action]` via phantom-agent) and `Signing::MultiSig.sign_as_co_signer_user_signed` (enriches the inner action's typed-data spec with `payloadMultiSigUser`+`outerSigner` address fields). Both mirror the Python SDK byte-for-byte; specs include fixture-based parity tests captured against `eth_account`+`msgpack`. Co-signature *collection* is the caller's responsibility — the SDK does not coordinate signing rooms.
|
|
69
|
+
|
|
68
70
|
### Numeric Conversion
|
|
69
71
|
|
|
70
72
|
- **`float_to_wire`** (in Exchange): converts to string with 8-decimal precision, validates rounding tolerance (`1e-12`), normalizes trailing zeros. No scientific notation.
|
data/lib/hyperliquid/exchange.rb
CHANGED
|
@@ -793,6 +793,40 @@ module Hyperliquid
|
|
|
793
793
|
post_action(action, signature, nonce, nil)
|
|
794
794
|
end
|
|
795
795
|
|
|
796
|
+
# Submit a multi-signature action wrapping any inner exchange action with N pre-collected
|
|
797
|
+
# co-signer signatures (`multiSig` user-signed envelope). The submitter's outer signature
|
|
798
|
+
# authorises execution; co-signer signatures must be collected externally via
|
|
799
|
+
# `Signing::MultiSig.sign_as_co_signer_l1` (for L1 inner actions) or
|
|
800
|
+
# `Signing::MultiSig.sign_as_co_signer_user_signed` (for user-signed inner actions).
|
|
801
|
+
# @param multi_sig_user [String] Address of the multi-sig user being acted on
|
|
802
|
+
# @param inner_action [Hash] The wrapped action body
|
|
803
|
+
# @param signatures [Array<Hash>] Co-signer signatures (each :r, :s, :v)
|
|
804
|
+
# @param nonce [Integer, nil] Nonce timestamp; defaults to `timestamp_ms`. Must match the
|
|
805
|
+
# nonce used by every co-signer when they signed.
|
|
806
|
+
# @param vault_address [String, nil] Optional vault address (must match co-signer hashes)
|
|
807
|
+
# @return [Hash] Exchange response
|
|
808
|
+
def multi_sig(multi_sig_user:, inner_action:, signatures:, nonce: nil, vault_address: nil)
|
|
809
|
+
nonce ||= timestamp_ms
|
|
810
|
+
envelope = Signing::MultiSig.build_envelope(
|
|
811
|
+
inner_action: inner_action,
|
|
812
|
+
multi_sig_user: multi_sig_user,
|
|
813
|
+
outer_signer: @signer.address,
|
|
814
|
+
signatures: signatures
|
|
815
|
+
)
|
|
816
|
+
multi_sig_action_hash = Signing::MultiSig.envelope_action_hash(
|
|
817
|
+
envelope: envelope,
|
|
818
|
+
nonce: nonce,
|
|
819
|
+
vault_address: vault_address,
|
|
820
|
+
expires_after: @expires_after
|
|
821
|
+
)
|
|
822
|
+
signature = @signer.sign_user_signed_action(
|
|
823
|
+
{ multiSigActionHash: multi_sig_action_hash, nonce: nonce },
|
|
824
|
+
Signing::MultiSig::OUTER_PRIMARY_TYPE,
|
|
825
|
+
Signing::EIP712::MULTI_SIG_TYPES
|
|
826
|
+
)
|
|
827
|
+
post_action(envelope, signature, nonce, vault_address)
|
|
828
|
+
end
|
|
829
|
+
|
|
796
830
|
# Claim accrued referral-program rewards (`claimRewards` L1 action).
|
|
797
831
|
# @return [Hash] Exchange response
|
|
798
832
|
def claim_rewards
|
|
@@ -893,6 +927,29 @@ module Hyperliquid
|
|
|
893
927
|
post_action(action, signature, nonce, nil)
|
|
894
928
|
end
|
|
895
929
|
|
|
930
|
+
# Create a new vault with the calling wallet as leader (`createVault` L1 action).
|
|
931
|
+
# @param name [String] Vault name (server-enforced 3–50 characters)
|
|
932
|
+
# @param description [String] Vault description (server-enforced 10–250 characters)
|
|
933
|
+
# @param initial_usd [Numeric] Initial USD seed (server-enforced $100 minimum);
|
|
934
|
+
# scaled internally to integer 1e6 micro-USD
|
|
935
|
+
|
|
936
|
+
# @return [Hash] Exchange response — on success `response.data` is the new vault address
|
|
937
|
+
def create_vault(name:, description:, initial_usd:)
|
|
938
|
+
nonce = timestamp_ms
|
|
939
|
+
action = {
|
|
940
|
+
type: 'createVault',
|
|
941
|
+
name: name,
|
|
942
|
+
description: description,
|
|
943
|
+
initialUsd: float_to_usd_int(initial_usd),
|
|
944
|
+
nonce: nonce
|
|
945
|
+
}
|
|
946
|
+
signature = @signer.sign_l1_action(
|
|
947
|
+
action, nonce,
|
|
948
|
+
expires_after: @expires_after
|
|
949
|
+
)
|
|
950
|
+
post_action(action, signature, nonce, nil)
|
|
951
|
+
end
|
|
952
|
+
|
|
896
953
|
# Borrow, lend, supply, or withdraw HIP-2 borrow/lend assets (`borrowLend` L1 action).
|
|
897
954
|
# Companion to the four HIP-2 info methods (`borrow_lend_user_state` etc.).
|
|
898
955
|
# @param operation [String] One of 'supply', 'withdraw', 'repay', 'borrow'
|
|
@@ -130,6 +130,14 @@ module Hyperliquid
|
|
|
130
130
|
]
|
|
131
131
|
}.freeze
|
|
132
132
|
|
|
133
|
+
MULTI_SIG_TYPES = {
|
|
134
|
+
'HyperliquidTransaction:SendMultiSig': [
|
|
135
|
+
{ name: :hyperliquidChain, type: 'string' },
|
|
136
|
+
{ name: :multiSigActionHash, type: 'bytes32' },
|
|
137
|
+
{ name: :nonce, type: 'uint64' }
|
|
138
|
+
]
|
|
139
|
+
}.freeze
|
|
140
|
+
|
|
133
141
|
SEND_TO_EVM_WITH_DATA_TYPES = {
|
|
134
142
|
'HyperliquidTransaction:SendToEvmWithData': [
|
|
135
143
|
{ name: :hyperliquidChain, type: 'string' },
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'msgpack'
|
|
4
|
+
|
|
5
|
+
module Hyperliquid
|
|
6
|
+
module Signing
|
|
7
|
+
# Multi-signature action helpers — submitter envelope construction and co-signer signing.
|
|
8
|
+
#
|
|
9
|
+
# Mirrors the official Python SDK's three multi-sig flows:
|
|
10
|
+
# 1. `Exchange#multi_sig` (the submitter — signs the outer envelope)
|
|
11
|
+
# 2. `sign_as_co_signer_l1` — co-signer for L1 inner actions (orders, cancels, etc.)
|
|
12
|
+
# 3. `sign_as_co_signer_user_signed` — co-signer for user-signed inner actions
|
|
13
|
+
# (sendAsset, usdSend, etc.)
|
|
14
|
+
#
|
|
15
|
+
# Co-signature collection is the caller's responsibility — these helpers produce a single
|
|
16
|
+
# co-signer signature; the submitter then assembles the array and posts the envelope.
|
|
17
|
+
module MultiSig
|
|
18
|
+
OUTER_PRIMARY_TYPE = 'HyperliquidTransaction:SendMultiSig'
|
|
19
|
+
|
|
20
|
+
# Build the outer multi-sig envelope (the action body posted to /exchange).
|
|
21
|
+
# @param inner_action [Hash] The wrapped action (any L1 or user-signed action body)
|
|
22
|
+
# @param multi_sig_user [String] Address of the multi-sig user (lowercased)
|
|
23
|
+
# @param outer_signer [String] Address of the submitter (lowercased)
|
|
24
|
+
# @param signatures [Array<Hash>] Pre-collected co-signer signatures
|
|
25
|
+
# @return [Hash] Multi-sig envelope ready for signing + posting
|
|
26
|
+
def self.build_envelope(inner_action:, multi_sig_user:, outer_signer:, signatures:)
|
|
27
|
+
{
|
|
28
|
+
type: 'multiSig',
|
|
29
|
+
signatureChainId: '0x66eee',
|
|
30
|
+
signatures: signatures,
|
|
31
|
+
payload: {
|
|
32
|
+
multiSigUser: multi_sig_user.downcase,
|
|
33
|
+
outerSigner: outer_signer.downcase,
|
|
34
|
+
action: inner_action
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Compute the multiSigActionHash that the submitter signs over.
|
|
40
|
+
# Mirrors Python's `sign_multi_sig_action`: action_hash(envelope - type, vault, nonce, expires).
|
|
41
|
+
# @param envelope [Hash] The multi-sig envelope (will have :type stripped before hashing)
|
|
42
|
+
# @param nonce [Integer] Nonce timestamp (ms)
|
|
43
|
+
# @param vault_address [String, nil] Optional vault address
|
|
44
|
+
# @param expires_after [Integer, nil] Optional expiration timestamp (ms)
|
|
45
|
+
# @return [String] 0x-prefixed 32-byte hex hash
|
|
46
|
+
def self.envelope_action_hash(envelope:, nonce:, vault_address: nil, expires_after: nil)
|
|
47
|
+
without_type = envelope.dup
|
|
48
|
+
without_type.delete(:type)
|
|
49
|
+
Signer.compute_action_hash(without_type, nonce, vault_address: vault_address,
|
|
50
|
+
expires_after: expires_after)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Co-signer flow for L1 inner actions (orders, cancels, leverage, etc.).
|
|
54
|
+
# The signed payload is the list `[multi_sig_user, outer_signer, action]`, hashed and
|
|
55
|
+
# signed via the L1 phantom-agent flow.
|
|
56
|
+
#
|
|
57
|
+
# @param signer [Signer] Co-signer's wallet
|
|
58
|
+
# @param inner_action [Hash] The wrapped L1 action
|
|
59
|
+
# @param multi_sig_user [String] Multi-sig user address
|
|
60
|
+
# @param outer_signer [String] Submitter address (NOT the co-signer's address)
|
|
61
|
+
# @param nonce [Integer] Nonce timestamp (ms)
|
|
62
|
+
# @param vault_address [String, nil] Optional vault address
|
|
63
|
+
# @param expires_after [Integer, nil] Optional expiration timestamp (ms)
|
|
64
|
+
# @return [Hash] Signature with :r, :s, :v
|
|
65
|
+
def self.sign_as_co_signer_l1(signer:, inner_action:, multi_sig_user:, outer_signer:, nonce:,
|
|
66
|
+
vault_address: nil, expires_after: nil)
|
|
67
|
+
envelope = [multi_sig_user.downcase, outer_signer.downcase, inner_action]
|
|
68
|
+
signer.sign_l1_action(envelope, nonce, vault_address: vault_address,
|
|
69
|
+
expires_after: expires_after)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Co-signer flow for user-signed inner actions (sendAsset, usdSend, etc.).
|
|
73
|
+
# Enriches the inner action with `payloadMultiSigUser` + `outerSigner` fields and the
|
|
74
|
+
# corresponding type entries, then signs via the user-signed typed-data flow.
|
|
75
|
+
#
|
|
76
|
+
# @param signer [Signer] Co-signer's wallet
|
|
77
|
+
# @param inner_action [Hash] The wrapped user-signed action body
|
|
78
|
+
# @param multi_sig_user [String] Multi-sig user address
|
|
79
|
+
# @param outer_signer [String] Submitter address
|
|
80
|
+
# @param primary_type [String] Inner action's EIP-712 primary type (e.g. 'HyperliquidTransaction:SendAsset')
|
|
81
|
+
# @param sign_types [Hash] Inner action's EIP-712 type definitions
|
|
82
|
+
# @return [Hash] Signature with :r, :s, :v
|
|
83
|
+
def self.sign_as_co_signer_user_signed(signer:, inner_action:, multi_sig_user:, outer_signer:,
|
|
84
|
+
primary_type:, sign_types:)
|
|
85
|
+
enriched_action = inner_action.merge(
|
|
86
|
+
payloadMultiSigUser: multi_sig_user.downcase,
|
|
87
|
+
outerSigner: outer_signer.downcase
|
|
88
|
+
)
|
|
89
|
+
enriched_types = enrich_user_signed_types(sign_types)
|
|
90
|
+
signer.sign_user_signed_action(enriched_action, primary_type, enriched_types)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Insert payloadMultiSigUser + outerSigner type entries immediately after hyperliquidChain.
|
|
94
|
+
# Matches Python SDK's `add_multi_sig_types`.
|
|
95
|
+
def self.enrich_user_signed_types(sign_types)
|
|
96
|
+
sign_types.each_with_object({}) do |(key, fields), result|
|
|
97
|
+
new_fields = []
|
|
98
|
+
fields.each do |f|
|
|
99
|
+
new_fields << f
|
|
100
|
+
if f[:name].to_s == 'hyperliquidChain'
|
|
101
|
+
new_fields << { name: :payloadMultiSigUser, type: 'address' }
|
|
102
|
+
new_fields << { name: :outerSigner, type: 'address' }
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
result[key] = new_fields
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
private_class_method :enrich_user_signed_types
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -50,6 +50,33 @@ module Hyperliquid
|
|
|
50
50
|
sign_typed_data(typed_data)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
# Compute the action hash used by the L1 phantom-agent flow and by the multi-sig
|
|
54
|
+
# outer-envelope flow. Mirrors the official Python SDK's `action_hash` byte layout:
|
|
55
|
+
# keccak256(msgpack(action) + nonce(8B BE) + vault_flag + [vault_addr] + [expires_flag + expires_after])
|
|
56
|
+
# @param action [Hash, Array] The action payload (or list-shaped envelope for multi-sig L1)
|
|
57
|
+
# @param nonce [Integer] Nonce timestamp (ms)
|
|
58
|
+
# @param vault_address [String, nil] Optional vault address (lowercased before hashing)
|
|
59
|
+
# @param expires_after [Integer, nil] Optional expiration timestamp (ms)
|
|
60
|
+
# @return [String] 0x-prefixed hex of the 32-byte keccak256 hash
|
|
61
|
+
def self.compute_action_hash(action, nonce, vault_address: nil, expires_after: nil)
|
|
62
|
+
data = action.to_msgpack
|
|
63
|
+
data += [nonce].pack('Q>')
|
|
64
|
+
|
|
65
|
+
if vault_address.nil?
|
|
66
|
+
data += "\x00"
|
|
67
|
+
else
|
|
68
|
+
data += "\x01"
|
|
69
|
+
data += [vault_address.sub(/\A0x/i, '').downcase].pack('H*')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
unless expires_after.nil?
|
|
73
|
+
data += "\x00"
|
|
74
|
+
data += [expires_after].pack('Q>')
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
"0x#{Eth::Util.keccak256(data).unpack1('H*')}"
|
|
78
|
+
end
|
|
79
|
+
|
|
53
80
|
# Sign a user-signed action (transfers, withdrawals, etc.)
|
|
54
81
|
# Uses direct EIP-712 typed data signing with HyperliquidSignTransaction domain
|
|
55
82
|
# @param action [Hash] The action message to sign (will have chain fields injected)
|
|
@@ -92,41 +119,14 @@ module Hyperliquid
|
|
|
92
119
|
# @param expires_after [Integer, nil] Optional expiration timestamp
|
|
93
120
|
# @return [Hash] Phantom agent with source and connectionId
|
|
94
121
|
def construct_phantom_agent(action, nonce, vault_address, expires_after)
|
|
95
|
-
# Compute action hash
|
|
96
|
-
# Maintains parity with official Python SDK
|
|
97
|
-
# data = msgpack(action) + nonce(8 bytes BE) + vault_flag + [vault_addr] + [expires_flag + expires_after]
|
|
98
|
-
# - Note: expires_flag is only included if expires_after exists. A bit odd but that's what the
|
|
99
|
-
# Python SDK does.
|
|
100
|
-
data = action.to_msgpack
|
|
101
|
-
data += [nonce].pack('Q>') # 8-byte big-endian uint64
|
|
102
|
-
|
|
103
|
-
if vault_address.nil?
|
|
104
|
-
data += "\x00" # no vault flag
|
|
105
|
-
else
|
|
106
|
-
data += "\x01" # has vault flag
|
|
107
|
-
data += address_to_bytes(vault_address.downcase)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
unless expires_after.nil?
|
|
111
|
-
data += "\x00" # expiration flag
|
|
112
|
-
data += [expires_after].pack('Q>') # 8-byte big-endian uint64
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
connection_id = Eth::Util.keccak256(data)
|
|
116
|
-
|
|
117
122
|
{
|
|
118
123
|
source: EIP712.source(testnet: @testnet),
|
|
119
|
-
connectionId:
|
|
124
|
+
connectionId: self.class.compute_action_hash(action, nonce,
|
|
125
|
+
vault_address: vault_address,
|
|
126
|
+
expires_after: expires_after)
|
|
120
127
|
}
|
|
121
128
|
end
|
|
122
129
|
|
|
123
|
-
# Convert hex address to 20-byte binary
|
|
124
|
-
# @param address [String] Ethereum address with 0x prefix
|
|
125
|
-
# @return [String] 20-byte binary representation
|
|
126
|
-
def address_to_bytes(address)
|
|
127
|
-
[address.sub(/\A0x/i, '')].pack('H*')
|
|
128
|
-
end
|
|
129
|
-
|
|
130
130
|
# Sign EIP-712 typed data using eth gem's built-in method
|
|
131
131
|
# @param typed_data [Hash] Complete EIP-712 structure
|
|
132
132
|
# @return [Hash] Signature components :r, :s, :v
|
|
@@ -141,13 +141,6 @@ module Hyperliquid
|
|
|
141
141
|
v: signature[128, 2].to_i(16)
|
|
142
142
|
}
|
|
143
143
|
end
|
|
144
|
-
|
|
145
|
-
# Convert binary data to hex string with 0x prefix
|
|
146
|
-
# @param bin [String] Binary data
|
|
147
|
-
# @return [String] Hex string with 0x prefix
|
|
148
|
-
def bin_to_hex(bin)
|
|
149
|
-
"0x#{bin.unpack1('H*')}"
|
|
150
|
-
end
|
|
151
144
|
end
|
|
152
145
|
end
|
|
153
146
|
end
|
data/lib/hyperliquid/version.rb
CHANGED
data/lib/hyperliquid.rb
CHANGED
|
@@ -8,6 +8,7 @@ require_relative 'hyperliquid/info'
|
|
|
8
8
|
require_relative 'hyperliquid/cloid'
|
|
9
9
|
require_relative 'hyperliquid/signing/eip712'
|
|
10
10
|
require_relative 'hyperliquid/signing/signer'
|
|
11
|
+
require_relative 'hyperliquid/signing/multi_sig'
|
|
11
12
|
require_relative 'hyperliquid/exchange'
|
|
12
13
|
require_relative 'hyperliquid/ws/client'
|
|
13
14
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Test 17: createVault (L1 exchange action)
|
|
5
|
+
#
|
|
6
|
+
# Creates a new vault with the calling wallet as leader, using a $100 testnet
|
|
7
|
+
# USDC seed (server-enforced minimum). On success, prints the new vault address
|
|
8
|
+
# from response.data.
|
|
9
|
+
#
|
|
10
|
+
# WARNING: This is a real testnet action. The $100 seed is locked into the
|
|
11
|
+
# created vault per Hyperliquid's vault lockup rules. Run intentionally —
|
|
12
|
+
# do NOT wire into test_automated.rb.
|
|
13
|
+
#
|
|
14
|
+
# Skips with a warning if the wallet has < $100 perp USDC available.
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_17_create_vault.rb
|
|
18
|
+
|
|
19
|
+
require_relative 'test_helpers'
|
|
20
|
+
|
|
21
|
+
sdk = build_sdk
|
|
22
|
+
separator('TEST 17: createVault')
|
|
23
|
+
|
|
24
|
+
state = sdk.info.user_state(sdk.exchange.address)
|
|
25
|
+
withdrawable = state['withdrawable'].to_f
|
|
26
|
+
puts "Wallet: #{sdk.exchange.address}"
|
|
27
|
+
puts "Withdrawable: $#{format('%.2f', withdrawable)}"
|
|
28
|
+
puts
|
|
29
|
+
|
|
30
|
+
if withdrawable < 100
|
|
31
|
+
puts red("SKIPPED: Need >= $100 perp USDC to seed a vault (have $#{format('%.2f', withdrawable)}).")
|
|
32
|
+
test_passed('Test 17 createVault')
|
|
33
|
+
exit 0
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
vault_name = "AgentVault#{Time.now.to_i}"
|
|
37
|
+
vault_description = 'Vault created by hyperliquid-run integration test (test_17).'
|
|
38
|
+
|
|
39
|
+
puts "Creating vault \"#{vault_name}\" with $100 seed..."
|
|
40
|
+
result = sdk.exchange.create_vault(
|
|
41
|
+
name: vault_name,
|
|
42
|
+
description: vault_description,
|
|
43
|
+
initial_usd: 100
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if api_error?(result)
|
|
47
|
+
puts red("createVault FAILED: #{result.inspect}")
|
|
48
|
+
test_passed('Test 17 createVault')
|
|
49
|
+
exit 1
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
unless result.is_a?(Hash) && result['status'] == 'ok'
|
|
53
|
+
$test_failed = true
|
|
54
|
+
puts red("Unexpected status: #{result.inspect}")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
response_type = result.dig('response', 'type')
|
|
58
|
+
unless response_type == 'createVault'
|
|
59
|
+
$test_failed = true
|
|
60
|
+
puts red("Expected response.type 'createVault', got: #{response_type.inspect}")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
vault_address = result.dig('response', 'data')
|
|
64
|
+
unless vault_address.is_a?(String) && vault_address.start_with?('0x') && vault_address.length == 42
|
|
65
|
+
$test_failed = true
|
|
66
|
+
puts red("Expected response.data to be a 0x-prefixed 40-hex-char vault address, got: #{vault_address.inspect}")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
unless $test_failed
|
|
70
|
+
puts green("createVault OK: status=ok, response.type=#{response_type}, vault=#{vault_address}")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
test_passed('Test 17 createVault')
|
data/scripts/test_all.rb
CHANGED
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: 1.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- carter2099
|
|
@@ -112,6 +112,7 @@ files:
|
|
|
112
112
|
- lib/hyperliquid/exchange.rb
|
|
113
113
|
- lib/hyperliquid/info.rb
|
|
114
114
|
- lib/hyperliquid/signing/eip712.rb
|
|
115
|
+
- lib/hyperliquid/signing/multi_sig.rb
|
|
115
116
|
- lib/hyperliquid/signing/signer.rb
|
|
116
117
|
- lib/hyperliquid/version.rb
|
|
117
118
|
- lib/hyperliquid/ws/client.rb
|
|
@@ -131,6 +132,7 @@ files:
|
|
|
131
132
|
- scripts/test_14_ws_candle.rb
|
|
132
133
|
- scripts/test_15_explorer.rb
|
|
133
134
|
- scripts/test_16_send_to_evm_with_data.rb
|
|
135
|
+
- scripts/test_17_create_vault.rb
|
|
134
136
|
- scripts/test_all.rb
|
|
135
137
|
- scripts/test_automated.rb
|
|
136
138
|
- scripts/test_helpers.rb
|