hyperliquid 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +13 -1
- data/CLAUDE.md +6 -7
- data/docs/API.md +14 -1
- data/docs/DEVELOPMENT.md +31 -17
- data/docs/EXAMPLES.md +91 -0
- data/lib/hyperliquid/exchange.rb +195 -0
- data/lib/hyperliquid/signing/eip712.rb +73 -0
- data/lib/hyperliquid/signing/signer.rb +27 -2
- data/lib/hyperliquid/version.rb +1 -1
- data/scripts/test_01_spot_market_roundtrip.rb +48 -0
- data/scripts/test_02_spot_limit_order.rb +48 -0
- data/scripts/test_03_perp_market_roundtrip.rb +52 -0
- data/scripts/test_04_perp_limit_order.rb +52 -0
- data/scripts/test_05_update_leverage.rb +39 -0
- data/scripts/test_06_modify_order.rb +67 -0
- data/scripts/test_07_market_close.rb +49 -0
- data/scripts/test_08_usd_class_transfer.rb +23 -0
- data/scripts/test_09_sub_account_lifecycle.rb +51 -0
- data/scripts/test_10_vault_transfer.rb +41 -0
- data/scripts/test_all.rb +86 -0
- data/scripts/test_helpers.rb +100 -0
- data/test_integration.rb +8 -367
- metadata +13 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 324ff762cbf5edcea7fc532f853b7295ffcd34476f6273fdef4e03bcc0f9eaee
|
|
4
|
+
data.tar.gz: 98b1b88b378bb8ae010301c656c833282cd406822fa0c8a7d6c134911b0f2cce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fe9e447e5500312f98e8f8e602071372847963daacfd51475e92d4db5a7bc988546e4cf106479a9c899bcecf113612c2aa9ebe0dd50a5384bd438bb0d6429865
|
|
7
|
+
data.tar.gz: 7cd7d2e048c4bf25e128dd7fd41814621bd95305d613199cb4c41abdf1901fdc4c665635000112aafeb7c395dbf01dae81cba74042557d86643c6ac169e3d201
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
## [Ruby Hyperliquid SDK Changelog]
|
|
2
2
|
|
|
3
|
+
## [0.6.0] - 2026-01-28
|
|
4
|
+
|
|
5
|
+
- Add transfers and account management to Exchange API
|
|
6
|
+
- USD transfers: `usd_send`, `usd_class_transfer`, `withdraw_from_bridge`
|
|
7
|
+
- Spot transfers: `spot_send`, `send_asset`
|
|
8
|
+
- Sub-accounts: `create_sub_account`, `sub_account_transfer`, `sub_account_spot_transfer`
|
|
9
|
+
- Vaults: `vault_transfer`
|
|
10
|
+
- Referrals: `set_referrer`
|
|
11
|
+
- Add user-signed action signing (`sign_user_signed_action`) for EIP-712 typed data with `HyperliquidSignTransaction` domain
|
|
12
|
+
- Add Python SDK parity test vectors for `usd_send`, `withdraw_from_bridge`, `create_sub_account`, and `sub_account_transfer`
|
|
13
|
+
- Reorganize integration tests into individual scripts under `scripts/` for easier debugging
|
|
14
|
+
|
|
3
15
|
## [0.5.0] - 2026-01-28
|
|
4
16
|
|
|
5
|
-
- Add core trading features to Exchange API
|
|
17
|
+
- Add core trading features to Exchange API
|
|
6
18
|
- Order modification: `modify_order`, `batch_modify`
|
|
7
19
|
- Position management: `update_leverage`, `update_isolated_margin`, `market_close`
|
|
8
20
|
- Dead man's switch: `schedule_cancel`
|
data/CLAUDE.md
CHANGED
|
@@ -45,16 +45,15 @@ ruby example.rb
|
|
|
45
45
|
|
|
46
46
|
### Integration Testing (Testnet)
|
|
47
47
|
```bash
|
|
48
|
-
# Run
|
|
48
|
+
# Run all integration tests (requires private key)
|
|
49
49
|
# Get testnet funds from: https://app.hyperliquid-testnet.xyz
|
|
50
|
-
HYPERLIQUID_PRIVATE_KEY=0x... ruby
|
|
50
|
+
HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_all.rb
|
|
51
|
+
|
|
52
|
+
# Run a single integration test
|
|
53
|
+
HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_08_usd_class_transfer.rb
|
|
51
54
|
```
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
1. Spot market roundtrip (buy/sell PURR/USDC)
|
|
55
|
-
2. Spot limit order (place and cancel)
|
|
56
|
-
3. Perp market roundtrip (long/close BTC)
|
|
57
|
-
4. Perp limit order (place short, cancel)
|
|
56
|
+
Integration tests live in `scripts/` as individual files. Each can be run standalone for debugging. `test_integration.rb` at the project root is a convenience wrapper that runs them all.
|
|
58
57
|
|
|
59
58
|
### Setup
|
|
60
59
|
```bash
|
data/docs/API.md
CHANGED
|
@@ -84,11 +84,24 @@ Read-only methods for querying market data and user information.
|
|
|
84
84
|
- `update_leverage(coin:, leverage:, is_cross:, ...)` - Set cross or isolated leverage for a coin
|
|
85
85
|
- `update_isolated_margin(coin:, amount:, ...)` - Add or remove isolated margin for a position
|
|
86
86
|
|
|
87
|
+
### Transfers & Account Management
|
|
88
|
+
|
|
89
|
+
- `usd_send(amount:, destination:)` - Transfer USDC to another address
|
|
90
|
+
- `spot_send(amount:, destination:, token:)` - Transfer a spot token to another address
|
|
91
|
+
- `usd_class_transfer(amount:, to_perp:)` - Move USDC between perp and spot accounts
|
|
92
|
+
- `withdraw_from_bridge(amount:, destination:)` - Withdraw USDC via the bridge
|
|
93
|
+
- `send_asset(destination:, source_dex:, destination_dex:, token:, amount:)` - Move assets between DEX instances
|
|
94
|
+
- `create_sub_account(name:)` - Create a sub-account
|
|
95
|
+
- `sub_account_transfer(sub_account_user:, is_deposit:, usd:)` - Transfer USDC to/from a sub-account
|
|
96
|
+
- `sub_account_spot_transfer(sub_account_user:, is_deposit:, token:, amount:)` - Transfer spot tokens to/from a sub-account
|
|
97
|
+
- `vault_transfer(vault_address:, is_deposit:, usd:)` - Deposit or withdraw USDC to/from a vault
|
|
98
|
+
- `set_referrer(code:)` - Set referral code
|
|
99
|
+
|
|
87
100
|
### Other
|
|
88
101
|
|
|
89
102
|
- `address` - Get the wallet address associated with the private key
|
|
90
103
|
|
|
91
|
-
|
|
104
|
+
Order placement and management methods support an optional `vault_address:` parameter for vault trading.
|
|
92
105
|
|
|
93
106
|
### Order Types
|
|
94
107
|
|
data/docs/DEVELOPMENT.md
CHANGED
|
@@ -11,13 +11,42 @@ bin/setup
|
|
|
11
11
|
## Running Tests
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
# Run all tests
|
|
14
|
+
# Run all unit tests
|
|
15
15
|
rake spec
|
|
16
16
|
|
|
17
|
-
# Run tests and linting together (default)
|
|
17
|
+
# Run unit tests and linting together (default)
|
|
18
18
|
rake
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
### Integration Testing (Testnet)
|
|
22
|
+
|
|
23
|
+
Integration tests live in `scripts/` and execute real trades on testnet. No real funds are at risk. Get testnet funds from https://app.hyperliquid-testnet.xyz.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Run all integration tests
|
|
27
|
+
HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_all.rb
|
|
28
|
+
|
|
29
|
+
# Run a single integration test
|
|
30
|
+
HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_08_usd_class_transfer.rb
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The convenience wrapper `ruby test_integration.rb` also runs all tests.
|
|
34
|
+
|
|
35
|
+
Available test scripts:
|
|
36
|
+
|
|
37
|
+
| Script | Description |
|
|
38
|
+
|--------|-------------|
|
|
39
|
+
| `test_01_spot_market_roundtrip.rb` | Buy/sell PURR/USDC at market |
|
|
40
|
+
| `test_02_spot_limit_order.rb` | Place and cancel a spot limit order |
|
|
41
|
+
| `test_03_perp_market_roundtrip.rb` | Long/close BTC at market |
|
|
42
|
+
| `test_04_perp_limit_order.rb` | Place and cancel a perp short |
|
|
43
|
+
| `test_05_update_leverage.rb` | Set cross, isolated, and reset leverage |
|
|
44
|
+
| `test_06_modify_order.rb` | Place, modify, and cancel an order |
|
|
45
|
+
| `test_07_market_close.rb` | Open a position and close via `market_close` |
|
|
46
|
+
| `test_08_usd_class_transfer.rb` | Transfer USDC between perp and spot |
|
|
47
|
+
| `test_09_sub_account_lifecycle.rb` | Create sub-account, deposit, withdraw |
|
|
48
|
+
| `test_10_vault_transfer.rb` | Deposit/withdraw to a vault |
|
|
49
|
+
|
|
21
50
|
## Linting
|
|
22
51
|
|
|
23
52
|
```bash
|
|
@@ -37,18 +66,3 @@ This opens an interactive prompt with the SDK loaded for experimentation.
|
|
|
37
66
|
```bash
|
|
38
67
|
ruby example.rb
|
|
39
68
|
```
|
|
40
|
-
|
|
41
|
-
## Integration Testing (Testnet)
|
|
42
|
-
|
|
43
|
-
For real trading tests on testnet:
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
# Get testnet funds from: https://app.hyperliquid-testnet.xyz
|
|
47
|
-
HYPERLIQUID_PRIVATE_KEY=0x... ruby test_integration.rb
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
The integration test executes real trades on testnet:
|
|
51
|
-
1. Spot market roundtrip (buy/sell PURR/USDC)
|
|
52
|
-
2. Spot limit order (place and cancel)
|
|
53
|
-
3. Perp market roundtrip (long/close BTC)
|
|
54
|
-
4. Perp limit order (place short, cancel)
|
data/docs/EXAMPLES.md
CHANGED
|
@@ -388,6 +388,97 @@ sdk.exchange.order(
|
|
|
388
388
|
)
|
|
389
389
|
```
|
|
390
390
|
|
|
391
|
+
### Transfers & Account Management
|
|
392
|
+
|
|
393
|
+
```ruby
|
|
394
|
+
# Transfer USDC to another address
|
|
395
|
+
sdk.exchange.usd_send(
|
|
396
|
+
amount: '100',
|
|
397
|
+
destination: '0x...'
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
# Transfer a spot token to another address
|
|
401
|
+
sdk.exchange.spot_send(
|
|
402
|
+
amount: '50',
|
|
403
|
+
destination: '0x...',
|
|
404
|
+
token: 'PURR'
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
# Move USDC between perp and spot accounts
|
|
408
|
+
sdk.exchange.usd_class_transfer(amount: '100', to_perp: false) # perp -> spot
|
|
409
|
+
sdk.exchange.usd_class_transfer(amount: '100', to_perp: true) # spot -> perp
|
|
410
|
+
|
|
411
|
+
# Withdraw USDC via the bridge
|
|
412
|
+
sdk.exchange.withdraw_from_bridge(
|
|
413
|
+
amount: '100',
|
|
414
|
+
destination: '0x...'
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
# Move assets between DEX instances
|
|
418
|
+
sdk.exchange.send_asset(
|
|
419
|
+
destination: '0x...',
|
|
420
|
+
source_dex: 'dex1',
|
|
421
|
+
destination_dex: 'dex2',
|
|
422
|
+
token: 'USDC',
|
|
423
|
+
amount: '100'
|
|
424
|
+
)
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Sub-Account Management
|
|
428
|
+
|
|
429
|
+
```ruby
|
|
430
|
+
# Create a sub-account
|
|
431
|
+
result = sdk.exchange.create_sub_account(name: 'my-sub-account')
|
|
432
|
+
sub_address = result.dig('response', 'data', 'subAccountUser')
|
|
433
|
+
|
|
434
|
+
# Deposit USDC into a sub-account
|
|
435
|
+
sdk.exchange.sub_account_transfer(
|
|
436
|
+
sub_account_user: sub_address,
|
|
437
|
+
is_deposit: true,
|
|
438
|
+
usd: 100
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
# Withdraw USDC from a sub-account
|
|
442
|
+
sdk.exchange.sub_account_transfer(
|
|
443
|
+
sub_account_user: sub_address,
|
|
444
|
+
is_deposit: false,
|
|
445
|
+
usd: 50
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
# Transfer spot tokens to a sub-account
|
|
449
|
+
sdk.exchange.sub_account_spot_transfer(
|
|
450
|
+
sub_account_user: sub_address,
|
|
451
|
+
is_deposit: true,
|
|
452
|
+
token: 'PURR',
|
|
453
|
+
amount: '10'
|
|
454
|
+
)
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Vault Operations
|
|
458
|
+
|
|
459
|
+
```ruby
|
|
460
|
+
# Deposit USDC into a vault
|
|
461
|
+
sdk.exchange.vault_transfer(
|
|
462
|
+
vault_address: '0x...',
|
|
463
|
+
is_deposit: true,
|
|
464
|
+
usd: 100
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# Withdraw USDC from a vault
|
|
468
|
+
sdk.exchange.vault_transfer(
|
|
469
|
+
vault_address: '0x...',
|
|
470
|
+
is_deposit: false,
|
|
471
|
+
usd: 50
|
|
472
|
+
)
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Referral
|
|
476
|
+
|
|
477
|
+
```ruby
|
|
478
|
+
# Set a referral code
|
|
479
|
+
sdk.exchange.set_referrer(code: 'MY_REFERRAL_CODE')
|
|
480
|
+
```
|
|
481
|
+
|
|
391
482
|
### Client Order IDs (Cloid)
|
|
392
483
|
|
|
393
484
|
```ruby
|
data/lib/hyperliquid/exchange.rb
CHANGED
|
@@ -374,6 +374,201 @@ module Hyperliquid
|
|
|
374
374
|
)
|
|
375
375
|
end
|
|
376
376
|
|
|
377
|
+
# Transfer USDC to another address
|
|
378
|
+
# @param amount [String, Numeric] Amount to send
|
|
379
|
+
# @param destination [String] Destination wallet address
|
|
380
|
+
# @return [Hash] Transfer response
|
|
381
|
+
def usd_send(amount:, destination:)
|
|
382
|
+
nonce = timestamp_ms
|
|
383
|
+
action = {
|
|
384
|
+
type: 'usdSend',
|
|
385
|
+
signatureChainId: '0x66eee',
|
|
386
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @signer.instance_variable_get(:@testnet)),
|
|
387
|
+
destination: destination,
|
|
388
|
+
amount: amount.to_s,
|
|
389
|
+
time: nonce
|
|
390
|
+
}
|
|
391
|
+
signature = @signer.sign_user_signed_action(
|
|
392
|
+
{ destination: destination, amount: amount.to_s, time: nonce },
|
|
393
|
+
'HyperliquidTransaction:UsdSend',
|
|
394
|
+
Signing::EIP712::USD_SEND_TYPES
|
|
395
|
+
)
|
|
396
|
+
post_action(action, signature, nonce, nil)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Transfer a spot token to another address
|
|
400
|
+
# @param amount [String, Numeric] Amount to send
|
|
401
|
+
# @param destination [String] Destination wallet address
|
|
402
|
+
# @param token [String] Token identifier
|
|
403
|
+
# @return [Hash] Transfer response
|
|
404
|
+
def spot_send(amount:, destination:, token:)
|
|
405
|
+
nonce = timestamp_ms
|
|
406
|
+
action = {
|
|
407
|
+
type: 'spotSend',
|
|
408
|
+
signatureChainId: '0x66eee',
|
|
409
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @signer.instance_variable_get(:@testnet)),
|
|
410
|
+
destination: destination,
|
|
411
|
+
token: token,
|
|
412
|
+
amount: amount.to_s,
|
|
413
|
+
time: nonce
|
|
414
|
+
}
|
|
415
|
+
signature = @signer.sign_user_signed_action(
|
|
416
|
+
{ destination: destination, token: token, amount: amount.to_s, time: nonce },
|
|
417
|
+
'HyperliquidTransaction:SpotSend',
|
|
418
|
+
Signing::EIP712::SPOT_SEND_TYPES
|
|
419
|
+
)
|
|
420
|
+
post_action(action, signature, nonce, nil)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
# Move USDC between perp and spot accounts
|
|
424
|
+
# @param amount [String, Numeric] Amount to transfer
|
|
425
|
+
# @param to_perp [Boolean] True to move to perp, false to move to spot
|
|
426
|
+
# @return [Hash] Transfer response
|
|
427
|
+
def usd_class_transfer(amount:, to_perp:)
|
|
428
|
+
nonce = timestamp_ms
|
|
429
|
+
action = {
|
|
430
|
+
type: 'usdClassTransfer',
|
|
431
|
+
signatureChainId: '0x66eee',
|
|
432
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @signer.instance_variable_get(:@testnet)),
|
|
433
|
+
amount: amount.to_s,
|
|
434
|
+
toPerp: to_perp,
|
|
435
|
+
nonce: nonce
|
|
436
|
+
}
|
|
437
|
+
signature = @signer.sign_user_signed_action(
|
|
438
|
+
{ amount: amount.to_s, toPerp: to_perp, nonce: nonce },
|
|
439
|
+
'HyperliquidTransaction:UsdClassTransfer',
|
|
440
|
+
Signing::EIP712::USD_CLASS_TRANSFER_TYPES
|
|
441
|
+
)
|
|
442
|
+
post_action(action, signature, nonce, nil)
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# Withdraw USDC via the bridge
|
|
446
|
+
# @param amount [String, Numeric] Amount to withdraw
|
|
447
|
+
# @param destination [String] Destination wallet address
|
|
448
|
+
# @return [Hash] Withdrawal response
|
|
449
|
+
def withdraw_from_bridge(amount:, destination:)
|
|
450
|
+
nonce = timestamp_ms
|
|
451
|
+
action = {
|
|
452
|
+
type: 'withdraw3',
|
|
453
|
+
signatureChainId: '0x66eee',
|
|
454
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @signer.instance_variable_get(:@testnet)),
|
|
455
|
+
destination: destination,
|
|
456
|
+
amount: amount.to_s,
|
|
457
|
+
time: nonce
|
|
458
|
+
}
|
|
459
|
+
signature = @signer.sign_user_signed_action(
|
|
460
|
+
{ destination: destination, amount: amount.to_s, time: nonce },
|
|
461
|
+
'HyperliquidTransaction:Withdraw',
|
|
462
|
+
Signing::EIP712::WITHDRAW_TYPES
|
|
463
|
+
)
|
|
464
|
+
post_action(action, signature, nonce, nil)
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# Move assets between DEX instances
|
|
468
|
+
# @param destination [String] Destination wallet address
|
|
469
|
+
# @param source_dex [String] Source DEX identifier
|
|
470
|
+
# @param destination_dex [String] Destination DEX identifier
|
|
471
|
+
# @param token [String] Token identifier
|
|
472
|
+
# @param amount [String, Numeric] Amount to send
|
|
473
|
+
# @return [Hash] Transfer response
|
|
474
|
+
def send_asset(destination:, source_dex:, destination_dex:, token:, amount:)
|
|
475
|
+
nonce = timestamp_ms
|
|
476
|
+
action = {
|
|
477
|
+
type: 'sendAsset',
|
|
478
|
+
signatureChainId: '0x66eee',
|
|
479
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @signer.instance_variable_get(:@testnet)),
|
|
480
|
+
destination: destination,
|
|
481
|
+
sourceDex: source_dex,
|
|
482
|
+
destinationDex: destination_dex,
|
|
483
|
+
token: token,
|
|
484
|
+
amount: amount.to_s,
|
|
485
|
+
fromSubAccount: '',
|
|
486
|
+
nonce: nonce
|
|
487
|
+
}
|
|
488
|
+
signature = @signer.sign_user_signed_action(
|
|
489
|
+
{
|
|
490
|
+
destination: destination, sourceDex: source_dex, destinationDex: destination_dex,
|
|
491
|
+
token: token, amount: amount.to_s, fromSubAccount: '', nonce: nonce
|
|
492
|
+
},
|
|
493
|
+
'HyperliquidTransaction:SendAsset',
|
|
494
|
+
Signing::EIP712::SEND_ASSET_TYPES
|
|
495
|
+
)
|
|
496
|
+
post_action(action, signature, nonce, nil)
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
# Create a sub-account
|
|
500
|
+
# @param name [String] Sub-account name
|
|
501
|
+
# @return [Hash] Creation response
|
|
502
|
+
def create_sub_account(name:)
|
|
503
|
+
nonce = timestamp_ms
|
|
504
|
+
action = { type: 'createSubAccount', name: name }
|
|
505
|
+
signature = @signer.sign_l1_action(action, nonce)
|
|
506
|
+
post_action(action, signature, nonce, nil)
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
# Transfer USDC to/from a sub-account
|
|
510
|
+
# @param sub_account_user [String] Sub-account wallet address
|
|
511
|
+
# @param is_deposit [Boolean] True to deposit into sub-account, false to withdraw
|
|
512
|
+
# @param usd [Numeric] Amount in USD
|
|
513
|
+
# @return [Hash] Transfer response
|
|
514
|
+
def sub_account_transfer(sub_account_user:, is_deposit:, usd:)
|
|
515
|
+
nonce = timestamp_ms
|
|
516
|
+
action = {
|
|
517
|
+
type: 'subAccountTransfer',
|
|
518
|
+
subAccountUser: sub_account_user,
|
|
519
|
+
isDeposit: is_deposit,
|
|
520
|
+
usd: float_to_usd_int(usd)
|
|
521
|
+
}
|
|
522
|
+
signature = @signer.sign_l1_action(action, nonce)
|
|
523
|
+
post_action(action, signature, nonce, nil)
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
# Transfer spot tokens to/from a sub-account
|
|
527
|
+
# @param sub_account_user [String] Sub-account wallet address
|
|
528
|
+
# @param is_deposit [Boolean] True to deposit into sub-account, false to withdraw
|
|
529
|
+
# @param token [String] Token identifier
|
|
530
|
+
# @param amount [String, Numeric] Amount to transfer
|
|
531
|
+
# @return [Hash] Transfer response
|
|
532
|
+
def sub_account_spot_transfer(sub_account_user:, is_deposit:, token:, amount:)
|
|
533
|
+
nonce = timestamp_ms
|
|
534
|
+
action = {
|
|
535
|
+
type: 'subAccountSpotTransfer',
|
|
536
|
+
subAccountUser: sub_account_user,
|
|
537
|
+
isDeposit: is_deposit,
|
|
538
|
+
token: token,
|
|
539
|
+
amount: amount.to_s
|
|
540
|
+
}
|
|
541
|
+
signature = @signer.sign_l1_action(action, nonce)
|
|
542
|
+
post_action(action, signature, nonce, nil)
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
# Deposit or withdraw USDC to/from a vault
|
|
546
|
+
# @param vault_address [String] Vault wallet address
|
|
547
|
+
# @param is_deposit [Boolean] True to deposit, false to withdraw
|
|
548
|
+
# @param usd [Numeric] Amount in USD
|
|
549
|
+
# @return [Hash] Vault transfer response
|
|
550
|
+
def vault_transfer(vault_address:, is_deposit:, usd:)
|
|
551
|
+
nonce = timestamp_ms
|
|
552
|
+
action = {
|
|
553
|
+
type: 'vaultTransfer',
|
|
554
|
+
vaultAddress: vault_address,
|
|
555
|
+
isDeposit: is_deposit,
|
|
556
|
+
usd: float_to_usd_int(usd)
|
|
557
|
+
}
|
|
558
|
+
signature = @signer.sign_l1_action(action, nonce)
|
|
559
|
+
post_action(action, signature, nonce, nil)
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
# Set referral code
|
|
563
|
+
# @param code [String] Referral code
|
|
564
|
+
# @return [Hash] Set referrer response
|
|
565
|
+
def set_referrer(code:)
|
|
566
|
+
nonce = timestamp_ms
|
|
567
|
+
action = { type: 'setReferrer', code: code }
|
|
568
|
+
signature = @signer.sign_l1_action(action, nonce)
|
|
569
|
+
post_action(action, signature, nonce, nil)
|
|
570
|
+
end
|
|
571
|
+
|
|
377
572
|
# Clear the asset metadata cache
|
|
378
573
|
# Call this if metadata has been updated
|
|
379
574
|
def reload_metadata!
|
|
@@ -12,6 +12,61 @@ module Hyperliquid
|
|
|
12
12
|
MAINNET_SOURCE = 'a'
|
|
13
13
|
TESTNET_SOURCE = 'b'
|
|
14
14
|
|
|
15
|
+
# Chain ID for user-signed actions (Arbitrum Sepolia: 0x66eee = 421614)
|
|
16
|
+
USER_SIGNED_CHAIN_ID = 421_614
|
|
17
|
+
|
|
18
|
+
# EIP-712 type definitions for user-signed actions
|
|
19
|
+
|
|
20
|
+
USD_SEND_TYPES = {
|
|
21
|
+
'HyperliquidTransaction:UsdSend': [
|
|
22
|
+
{ name: :hyperliquidChain, type: 'string' },
|
|
23
|
+
{ name: :destination, type: 'string' },
|
|
24
|
+
{ name: :amount, type: 'string' },
|
|
25
|
+
{ name: :time, type: 'uint64' }
|
|
26
|
+
]
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
SPOT_SEND_TYPES = {
|
|
30
|
+
'HyperliquidTransaction:SpotSend': [
|
|
31
|
+
{ name: :hyperliquidChain, type: 'string' },
|
|
32
|
+
{ name: :destination, type: 'string' },
|
|
33
|
+
{ name: :token, type: 'string' },
|
|
34
|
+
{ name: :amount, type: 'string' },
|
|
35
|
+
{ name: :time, type: 'uint64' }
|
|
36
|
+
]
|
|
37
|
+
}.freeze
|
|
38
|
+
|
|
39
|
+
USD_CLASS_TRANSFER_TYPES = {
|
|
40
|
+
'HyperliquidTransaction:UsdClassTransfer': [
|
|
41
|
+
{ name: :hyperliquidChain, type: 'string' },
|
|
42
|
+
{ name: :amount, type: 'string' },
|
|
43
|
+
{ name: :toPerp, type: 'bool' },
|
|
44
|
+
{ name: :nonce, type: 'uint64' }
|
|
45
|
+
]
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
48
|
+
WITHDRAW_TYPES = {
|
|
49
|
+
'HyperliquidTransaction:Withdraw': [
|
|
50
|
+
{ name: :hyperliquidChain, type: 'string' },
|
|
51
|
+
{ name: :destination, type: 'string' },
|
|
52
|
+
{ name: :amount, type: 'string' },
|
|
53
|
+
{ name: :time, type: 'uint64' }
|
|
54
|
+
]
|
|
55
|
+
}.freeze
|
|
56
|
+
|
|
57
|
+
SEND_ASSET_TYPES = {
|
|
58
|
+
'HyperliquidTransaction:SendAsset': [
|
|
59
|
+
{ name: :hyperliquidChain, type: 'string' },
|
|
60
|
+
{ name: :destination, type: 'string' },
|
|
61
|
+
{ name: :sourceDex, type: 'string' },
|
|
62
|
+
{ name: :destinationDex, type: 'string' },
|
|
63
|
+
{ name: :token, type: 'string' },
|
|
64
|
+
{ name: :amount, type: 'string' },
|
|
65
|
+
{ name: :fromSubAccount, type: 'string' },
|
|
66
|
+
{ name: :nonce, type: 'uint64' }
|
|
67
|
+
]
|
|
68
|
+
}.freeze
|
|
69
|
+
|
|
15
70
|
class << self
|
|
16
71
|
# Domain for L1 actions (orders, cancels, leverage, etc.)
|
|
17
72
|
# @return [Hash] EIP-712 domain configuration
|
|
@@ -24,6 +79,17 @@ module Hyperliquid
|
|
|
24
79
|
}
|
|
25
80
|
end
|
|
26
81
|
|
|
82
|
+
# Domain for user-signed actions (transfers, withdrawals, etc.)
|
|
83
|
+
# @return [Hash] EIP-712 domain configuration
|
|
84
|
+
def user_signed_domain
|
|
85
|
+
{
|
|
86
|
+
name: 'HyperliquidSignTransaction',
|
|
87
|
+
version: '1',
|
|
88
|
+
chainId: USER_SIGNED_CHAIN_ID,
|
|
89
|
+
verifyingContract: '0x0000000000000000000000000000000000000000'
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
|
|
27
93
|
# EIP-712 domain type definition
|
|
28
94
|
# @return [Array<Hash>] Domain type fields
|
|
29
95
|
def domain_type
|
|
@@ -50,6 +116,13 @@ module Hyperliquid
|
|
|
50
116
|
def source(testnet:)
|
|
51
117
|
testnet ? TESTNET_SOURCE : MAINNET_SOURCE
|
|
52
118
|
end
|
|
119
|
+
|
|
120
|
+
# Get hyperliquid chain name for user-signed actions
|
|
121
|
+
# @param testnet [Boolean] Whether testnet
|
|
122
|
+
# @return [String] "Mainnet" or "Testnet"
|
|
123
|
+
def hyperliquid_chain(testnet:)
|
|
124
|
+
testnet ? 'Testnet' : 'Mainnet'
|
|
125
|
+
end
|
|
53
126
|
end
|
|
54
127
|
end
|
|
55
128
|
end
|
|
@@ -44,6 +44,31 @@ module Hyperliquid
|
|
|
44
44
|
sign_typed_data(typed_data)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
# Sign a user-signed action (transfers, withdrawals, etc.)
|
|
48
|
+
# Uses direct EIP-712 typed data signing with HyperliquidSignTransaction domain
|
|
49
|
+
# @param action [Hash] The action message to sign (will have chain fields injected)
|
|
50
|
+
# @param primary_type [String] EIP-712 primary type (e.g., "HyperliquidTransaction:UsdSend")
|
|
51
|
+
# @param sign_types [Hash] EIP-712 type definitions for the action
|
|
52
|
+
# @return [Hash] Signature with :r, :s, :v components
|
|
53
|
+
def sign_user_signed_action(action, primary_type, sign_types)
|
|
54
|
+
# Inject chain fields into a copy of the action
|
|
55
|
+
message = action.merge(
|
|
56
|
+
hyperliquidChain: EIP712.hyperliquid_chain(testnet: @testnet),
|
|
57
|
+
signatureChainId: '0x66eee'
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
typed_data = {
|
|
61
|
+
types: {
|
|
62
|
+
EIP712Domain: EIP712.domain_type
|
|
63
|
+
}.merge(sign_types),
|
|
64
|
+
primaryType: primary_type,
|
|
65
|
+
domain: EIP712.user_signed_domain,
|
|
66
|
+
message: message
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
sign_typed_data(typed_data)
|
|
70
|
+
end
|
|
71
|
+
|
|
47
72
|
private
|
|
48
73
|
|
|
49
74
|
# Normalize private key format
|
|
@@ -54,7 +79,7 @@ module Hyperliquid
|
|
|
54
79
|
end
|
|
55
80
|
|
|
56
81
|
# Construct the phantom agent for signing
|
|
57
|
-
# Maintains parity with Python SDK
|
|
82
|
+
# Maintains parity with official Python SDK
|
|
58
83
|
# @param action [Hash] Action payload
|
|
59
84
|
# @param nonce [Integer] Nonce timestamp
|
|
60
85
|
# @param vault_address [String, nil] Optional vault address
|
|
@@ -62,7 +87,7 @@ module Hyperliquid
|
|
|
62
87
|
# @return [Hash] Phantom agent with source and connectionId
|
|
63
88
|
def construct_phantom_agent(action, nonce, vault_address, expires_after)
|
|
64
89
|
# Compute action hash
|
|
65
|
-
# Maintains parity with Python SDK
|
|
90
|
+
# Maintains parity with official Python SDK
|
|
66
91
|
# data = msgpack(action) + nonce(8 bytes BE) + vault_flag + [vault_addr] + [expires_flag + expires_after]
|
|
67
92
|
# - Note: expires_flag is only included if expires_after exists. A bit odd but that's what the
|
|
68
93
|
# Python SDK does.
|
data/lib/hyperliquid/version.rb
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Test 1: Spot Market Roundtrip (PURR/USDC)
|
|
5
|
+
# Buy and sell PURR at market price.
|
|
6
|
+
|
|
7
|
+
require_relative 'test_helpers'
|
|
8
|
+
|
|
9
|
+
sdk = build_sdk
|
|
10
|
+
separator('TEST 1: Spot Market Roundtrip (PURR/USDC)')
|
|
11
|
+
|
|
12
|
+
spot_coin = 'PURR/USDC'
|
|
13
|
+
spot_size = 5
|
|
14
|
+
|
|
15
|
+
mids = sdk.info.all_mids
|
|
16
|
+
spot_price = mids[spot_coin]&.to_f
|
|
17
|
+
|
|
18
|
+
if spot_price&.positive?
|
|
19
|
+
puts "#{spot_coin} mid: $#{spot_price}"
|
|
20
|
+
puts "Size: #{spot_size} PURR (~$#{(spot_size * spot_price).round(2)})"
|
|
21
|
+
puts "Slippage: #{(SPOT_SLIPPAGE * 100).to_i}%"
|
|
22
|
+
puts
|
|
23
|
+
|
|
24
|
+
puts 'Placing market BUY...'
|
|
25
|
+
result = sdk.exchange.market_order(
|
|
26
|
+
coin: spot_coin,
|
|
27
|
+
is_buy: true,
|
|
28
|
+
size: spot_size,
|
|
29
|
+
slippage: SPOT_SLIPPAGE
|
|
30
|
+
)
|
|
31
|
+
check_result(result, 'Buy')
|
|
32
|
+
|
|
33
|
+
wait_with_countdown(WAIT_SECONDS, 'Waiting before sell...')
|
|
34
|
+
|
|
35
|
+
puts 'Placing market SELL...'
|
|
36
|
+
result = sdk.exchange.market_order(
|
|
37
|
+
coin: spot_coin,
|
|
38
|
+
is_buy: false,
|
|
39
|
+
size: spot_size,
|
|
40
|
+
slippage: SPOT_SLIPPAGE
|
|
41
|
+
)
|
|
42
|
+
check_result(result, 'Sell')
|
|
43
|
+
else
|
|
44
|
+
puts red("SKIPPED: Could not get #{spot_coin} price")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
test_passed('Test 1 Spot Market Roundtrip')
|
|
48
|
+
|