hyperliquid 1.5.0 → 1.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 +37 -0
- data/lib/hyperliquid/exchange.rb +279 -0
- data/lib/hyperliquid/info.rb +14 -0
- data/lib/hyperliquid/signing/eip712.rb +33 -0
- data/lib/hyperliquid/signing/multi_sig.rb +26 -1
- data/lib/hyperliquid/version.rb +1 -1
- data/scripts/test_18_user_portfolio_margin.rb +72 -0
- data/scripts/test_19_spot_user.rb +55 -0
- data/scripts/test_all.rb +3 -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: fa1051cb2277e69795819ed2ae8564e89ac3e6b34139d9112a8273f0604d2fa1
|
|
4
|
+
data.tar.gz: 072416d6751fa766f0b4eeb5c416365ea96652b980301932d3ab32a549750b24
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 41059a1ef505d93e061fdd575f42ca340c6eb1e8100f14adf176c9512bd65ce4c1afdd1ddeeb831a1833bce63b9ca065f602681c673bba9b96f7a298130b05a0
|
|
7
|
+
data.tar.gz: 9cfb58786186bafc873dc9dd1747c2b87a645770fddc8d759ea3b5420e7367f1118de056de8e470dbe8bff06e44e55033ce063d839a3c81803fc418e3be68890
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
## [Ruby Hyperliquid SDK Changelog]
|
|
2
2
|
|
|
3
|
+
## [1.7.0] - 2026-06-11
|
|
4
|
+
|
|
5
|
+
### New Exchange actions
|
|
6
|
+
|
|
7
|
+
- `Exchange#authorize_aqav2_role(token:, role:)` — L1 action authorizing an AQAv2 role (e.g. `"treasury"`); supports `expires_after`.
|
|
8
|
+
- `Exchange#staking_link_disable_trading_user(trading_user:)` — user-signed action linking a staking account to a trading user (irreversible). Adds `STAKING_LINK_DISABLE_TRADING_USER_TYPES`.
|
|
9
|
+
- `Exchange#finalize_evm_contract(input:)` — L1 action linking a HyperCore spot token to a HyperEVM ERC-20 contract. Accepts a `Hash` (`{create: {nonce:}}`) or string variant (`"firstStorageSlot"` / `"customStorageSlot"`).
|
|
10
|
+
- HIP-4 `userOutcome` variants (L1 actions, not user-signed): `split_outcome(question:, outcome:, amount:)`, `merge_outcome(question:, amount:)`, `merge_question(question:, amount:)`, `negate_outcome(question:, outcome:, amount:)`. `merge_outcome` and `merge_question` accept `amount: nil` for the max-available case.
|
|
11
|
+
|
|
12
|
+
### New Info methods
|
|
13
|
+
|
|
14
|
+
- `Info#settled_outcome(outcome:)` — returns settled prediction-market outcome data.
|
|
15
|
+
|
|
16
|
+
### Fixes
|
|
17
|
+
|
|
18
|
+
- `Signing::MultiSig.payload_action` now normalizes `userSetAbstraction` long-form abstraction values (`"disabled"`, `"unifiedAccount"`, `"portfolioMargin"`) to their wire enum equivalents (`"i"`, `"u"`, `"p"`) in the L1 payload posted to `/exchange`, matching Python SDK 0.24.0. Pre-translated short codes pass through unchanged.
|
|
19
|
+
|
|
20
|
+
## [1.6.0] - 2026-05-26
|
|
21
|
+
|
|
22
|
+
### New Exchange actions
|
|
23
|
+
|
|
24
|
+
- `Exchange#twap_order(asset:, is_buy:, sz:, reduce_only:, minutes:, randomize:, ...)` — L1 action that places a TWAP order with the nested `twap` dict shape (`a`/`b`/`s`/`r`/`m`/`t`). Size via `float_to_wire`; supports `vault_address`.
|
|
25
|
+
- `Exchange#twap_cancel(asset:, twap_id:, ...)` — L1 action that cancels an active TWAP by asset index + twap id; supports `vault_address`.
|
|
26
|
+
- `Exchange#reserve_request_weight(weight:, ...)` — L1 action with a weight-only body; supports `expires_after`, no `vault_address` per TS schema.
|
|
27
|
+
- `Exchange#c_deposit(wei:)` — user-signed action depositing native HYPE to staking. Adds `C_DEPOSIT_TYPES`.
|
|
28
|
+
- `Exchange#c_withdraw(wei:)` — user-signed action withdrawing native HYPE from staking. Adds `C_WITHDRAW_TYPES`.
|
|
29
|
+
- `Exchange#user_portfolio_margin(user:, enabled:)` — user-signed action toggling cross-portfolio-margin mode. Adds `USER_PORTFOLIO_MARGIN_TYPES`.
|
|
30
|
+
- `Exchange#spot_user(opt_out:)` — L1 action toggling spot-dusting opt-out (wraps the wire-shape `toggleSpotDusting` inner object); supports `expires_after`.
|
|
31
|
+
|
|
32
|
+
### New Info methods
|
|
33
|
+
|
|
34
|
+
- `Info#gossip_priority_auction_status` — returns the current `gossipPriorityAuctionStatus`.
|
|
35
|
+
|
|
36
|
+
### Tests
|
|
37
|
+
|
|
38
|
+
- New integration scripts `scripts/test_18_user_portfolio_margin.rb` and `scripts/test_19_spot_user.rb` wired into `test_all.rb` (not yet in `test_automated.rb`).
|
|
39
|
+
|
|
3
40
|
## [1.5.0] - 2026-05-08
|
|
4
41
|
|
|
5
42
|
### New Exchange actions
|
data/lib/hyperliquid/exchange.rb
CHANGED
|
@@ -1017,6 +1017,31 @@ module Hyperliquid
|
|
|
1017
1017
|
post_action(action, signature, nonce, nil)
|
|
1018
1018
|
end
|
|
1019
1019
|
|
|
1020
|
+
# Permanently disable a linked trading user, locking its funds
|
|
1021
|
+
# (`stakingLinkDisableTradingUser` user-signed action). Sent by the staking user.
|
|
1022
|
+
# After 1 year of locking, funds from the trading user are automatically transferred
|
|
1023
|
+
# to the staking user. **This action is irreversible.** The `trading_user` address is
|
|
1024
|
+
# lowercased to match the address-field convention used by other user-signed actions.
|
|
1025
|
+
# @param trading_user [String] Trading user address to disable
|
|
1026
|
+
# @return [Hash] Exchange response
|
|
1027
|
+
def staking_link_disable_trading_user(trading_user:)
|
|
1028
|
+
nonce = timestamp_ms
|
|
1029
|
+
trading_user_lower = trading_user.downcase
|
|
1030
|
+
action = {
|
|
1031
|
+
type: 'stakingLinkDisableTradingUser',
|
|
1032
|
+
signatureChainId: '0x66eee',
|
|
1033
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
1034
|
+
tradingUser: trading_user_lower,
|
|
1035
|
+
nonce: nonce
|
|
1036
|
+
}
|
|
1037
|
+
signature = @signer.sign_user_signed_action(
|
|
1038
|
+
{ tradingUser: trading_user_lower, nonce: nonce },
|
|
1039
|
+
'HyperliquidTransaction:StakingLinkDisableTradingUser',
|
|
1040
|
+
Signing::EIP712::STAKING_LINK_DISABLE_TRADING_USER_TYPES
|
|
1041
|
+
)
|
|
1042
|
+
post_action(action, signature, nonce, nil)
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1020
1045
|
# Move assets between DEX instances on behalf of an agent's principal
|
|
1021
1046
|
# (`agentSendAsset` L1 action). Unlike `send_asset` (which is user-signed),
|
|
1022
1047
|
# this is signed by an agent and the destination must equal the agent's
|
|
@@ -1110,6 +1135,260 @@ module Hyperliquid
|
|
|
1110
1135
|
post_action(action, signature, nonce, nil)
|
|
1111
1136
|
end
|
|
1112
1137
|
|
|
1138
|
+
# Toggle cross-portfolio-margin mode for a user (`userPortfolioMargin` user-signed action).
|
|
1139
|
+
# The `user` address is lowercased to match the Python SDK and protocol expectations.
|
|
1140
|
+
# @param user [String] Wallet address whose portfolio-margin mode is being toggled
|
|
1141
|
+
# @param enabled [Boolean] True to enable cross-portfolio margin, false to disable
|
|
1142
|
+
# @return [Hash] Exchange response
|
|
1143
|
+
def user_portfolio_margin(user:, enabled:)
|
|
1144
|
+
nonce = timestamp_ms
|
|
1145
|
+
user_lower = user.downcase
|
|
1146
|
+
action = {
|
|
1147
|
+
type: 'userPortfolioMargin',
|
|
1148
|
+
signatureChainId: '0x66eee',
|
|
1149
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
1150
|
+
user: user_lower,
|
|
1151
|
+
enabled: enabled,
|
|
1152
|
+
nonce: nonce
|
|
1153
|
+
}
|
|
1154
|
+
signature = @signer.sign_user_signed_action(
|
|
1155
|
+
{ user: user_lower, enabled: enabled, nonce: nonce },
|
|
1156
|
+
'HyperliquidTransaction:UserPortfolioMargin',
|
|
1157
|
+
Signing::EIP712::USER_PORTFOLIO_MARGIN_TYPES
|
|
1158
|
+
)
|
|
1159
|
+
post_action(action, signature, nonce, nil)
|
|
1160
|
+
end
|
|
1161
|
+
|
|
1162
|
+
# Deposit native HYPE from the user's spot account into staking (`cDeposit` user-signed action).
|
|
1163
|
+
# @param wei [Integer] Amount of wei to deposit into staking (float * 1e8)
|
|
1164
|
+
# @return [Hash] Exchange response
|
|
1165
|
+
def c_deposit(wei:)
|
|
1166
|
+
nonce = timestamp_ms
|
|
1167
|
+
wei_int = wei.to_i
|
|
1168
|
+
action = {
|
|
1169
|
+
type: 'cDeposit',
|
|
1170
|
+
signatureChainId: '0x66eee',
|
|
1171
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
1172
|
+
wei: wei_int,
|
|
1173
|
+
nonce: nonce
|
|
1174
|
+
}
|
|
1175
|
+
signature = @signer.sign_user_signed_action(
|
|
1176
|
+
{ wei: wei_int, nonce: nonce },
|
|
1177
|
+
'HyperliquidTransaction:CDeposit',
|
|
1178
|
+
Signing::EIP712::C_DEPOSIT_TYPES
|
|
1179
|
+
)
|
|
1180
|
+
post_action(action, signature, nonce, nil)
|
|
1181
|
+
end
|
|
1182
|
+
|
|
1183
|
+
# Withdraw native HYPE from staking back into the user's spot account (`cWithdraw` user-signed action).
|
|
1184
|
+
# @param wei [Integer] Amount of wei to withdraw from staking (float * 1e8)
|
|
1185
|
+
# @return [Hash] Exchange response
|
|
1186
|
+
def c_withdraw(wei:)
|
|
1187
|
+
nonce = timestamp_ms
|
|
1188
|
+
wei_int = wei.to_i
|
|
1189
|
+
action = {
|
|
1190
|
+
type: 'cWithdraw',
|
|
1191
|
+
signatureChainId: '0x66eee',
|
|
1192
|
+
hyperliquidChain: Signing::EIP712.hyperliquid_chain(testnet: @testnet),
|
|
1193
|
+
wei: wei_int,
|
|
1194
|
+
nonce: nonce
|
|
1195
|
+
}
|
|
1196
|
+
signature = @signer.sign_user_signed_action(
|
|
1197
|
+
{ wei: wei_int, nonce: nonce },
|
|
1198
|
+
'HyperliquidTransaction:CWithdraw',
|
|
1199
|
+
Signing::EIP712::C_WITHDRAW_TYPES
|
|
1200
|
+
)
|
|
1201
|
+
post_action(action, signature, nonce, nil)
|
|
1202
|
+
end
|
|
1203
|
+
|
|
1204
|
+
# Place a TWAP order (`twapOrder` L1 action). The order is sliced over `minutes`
|
|
1205
|
+
# minutes (5..1440). When `randomize` is true the protocol randomizes the timing
|
|
1206
|
+
# of individual child orders. `size` is denominated in base currency units.
|
|
1207
|
+
# @param coin [String] Asset symbol
|
|
1208
|
+
# @param is_buy [Boolean] True for buy/long, false for sell/short
|
|
1209
|
+
# @param size [String, Numeric] Total order size (base currency units)
|
|
1210
|
+
# @param reduce_only [Boolean] Reduce-only flag
|
|
1211
|
+
# @param minutes [Integer] TWAP duration in minutes (5..1440)
|
|
1212
|
+
# @param randomize [Boolean] Randomize order timing
|
|
1213
|
+
# @param vault_address [String, nil] Vault address if acting on behalf of a vault
|
|
1214
|
+
# @return [Hash] Exchange response — on success `response.data.status.running.twapId`
|
|
1215
|
+
def twap_order(coin:, is_buy:, size:, reduce_only:, minutes:, randomize:, vault_address: nil)
|
|
1216
|
+
nonce = timestamp_ms
|
|
1217
|
+
action = {
|
|
1218
|
+
type: 'twapOrder',
|
|
1219
|
+
twap: {
|
|
1220
|
+
a: asset_index(coin),
|
|
1221
|
+
b: is_buy,
|
|
1222
|
+
s: float_to_wire(size),
|
|
1223
|
+
r: reduce_only,
|
|
1224
|
+
m: minutes,
|
|
1225
|
+
t: randomize
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
signature = @signer.sign_l1_action(
|
|
1229
|
+
action, nonce,
|
|
1230
|
+
vault_address: vault_address,
|
|
1231
|
+
expires_after: @expires_after
|
|
1232
|
+
)
|
|
1233
|
+
post_action(action, signature, nonce, vault_address)
|
|
1234
|
+
end
|
|
1235
|
+
|
|
1236
|
+
# Cancel a TWAP order by id (`twapCancel` L1 action).
|
|
1237
|
+
# @param coin [String] Asset symbol the TWAP applies to
|
|
1238
|
+
# @param twap_id [Integer] TWAP id returned by `twap_order`
|
|
1239
|
+
# @param vault_address [String, nil] Vault address if acting on behalf of a vault
|
|
1240
|
+
# @return [Hash] Exchange response
|
|
1241
|
+
def twap_cancel(coin:, twap_id:, vault_address: nil)
|
|
1242
|
+
nonce = timestamp_ms
|
|
1243
|
+
action = { type: 'twapCancel', a: asset_index(coin), t: twap_id }
|
|
1244
|
+
signature = @signer.sign_l1_action(
|
|
1245
|
+
action, nonce,
|
|
1246
|
+
vault_address: vault_address,
|
|
1247
|
+
expires_after: @expires_after
|
|
1248
|
+
)
|
|
1249
|
+
post_action(action, signature, nonce, vault_address)
|
|
1250
|
+
end
|
|
1251
|
+
|
|
1252
|
+
# Reserve additional rate-limited actions for a fee (`reserveRequestWeight` L1 action).
|
|
1253
|
+
# @param weight [Integer] Amount of request weight to reserve
|
|
1254
|
+
# @return [Hash] Exchange response
|
|
1255
|
+
def reserve_request_weight(weight:)
|
|
1256
|
+
nonce = timestamp_ms
|
|
1257
|
+
action = { type: 'reserveRequestWeight', weight: weight }
|
|
1258
|
+
signature = @signer.sign_l1_action(
|
|
1259
|
+
action, nonce,
|
|
1260
|
+
expires_after: @expires_after
|
|
1261
|
+
)
|
|
1262
|
+
post_action(action, signature, nonce, nil)
|
|
1263
|
+
end
|
|
1264
|
+
|
|
1265
|
+
# HIP-4: split `amount` quote tokens into `amount` Yes and `amount` No shares
|
|
1266
|
+
# of an outcome (`userOutcome` L1 action, splitOutcome variant).
|
|
1267
|
+
# @param outcome [Integer] Outcome identifier
|
|
1268
|
+
# @param amount [String, Numeric] Amount of quote tokens to split (UnsignedDecimal, coerced via to_s)
|
|
1269
|
+
# @return [Hash] Exchange response
|
|
1270
|
+
def split_outcome(outcome:, amount:)
|
|
1271
|
+
nonce = timestamp_ms
|
|
1272
|
+
action = {
|
|
1273
|
+
type: 'userOutcome',
|
|
1274
|
+
splitOutcome: { outcome: outcome, amount: amount.to_s }
|
|
1275
|
+
}
|
|
1276
|
+
signature = @signer.sign_l1_action(
|
|
1277
|
+
action, nonce,
|
|
1278
|
+
expires_after: @expires_after
|
|
1279
|
+
)
|
|
1280
|
+
post_action(action, signature, nonce, nil)
|
|
1281
|
+
end
|
|
1282
|
+
|
|
1283
|
+
# HIP-4: merge `amount` Yes and `amount` No shares of an outcome back into
|
|
1284
|
+
# `amount` quote tokens (`userOutcome` L1 action, mergeOutcome variant).
|
|
1285
|
+
# Pass `amount: nil` to merge the maximum available.
|
|
1286
|
+
# @param outcome [Integer] Outcome identifier
|
|
1287
|
+
# @param amount [String, Numeric, nil] Amount of shares to merge; nil = maximum available
|
|
1288
|
+
# @return [Hash] Exchange response
|
|
1289
|
+
def merge_outcome(outcome:, amount: nil)
|
|
1290
|
+
nonce = timestamp_ms
|
|
1291
|
+
action = {
|
|
1292
|
+
type: 'userOutcome',
|
|
1293
|
+
mergeOutcome: { outcome: outcome, amount: amount&.to_s }
|
|
1294
|
+
}
|
|
1295
|
+
signature = @signer.sign_l1_action(
|
|
1296
|
+
action, nonce,
|
|
1297
|
+
expires_after: @expires_after
|
|
1298
|
+
)
|
|
1299
|
+
post_action(action, signature, nonce, nil)
|
|
1300
|
+
end
|
|
1301
|
+
|
|
1302
|
+
# HIP-4: merge `amount` Yes shares from every outcome of a question into
|
|
1303
|
+
# `amount` quote tokens (`userOutcome` L1 action, mergeQuestion variant).
|
|
1304
|
+
# Pass `amount: nil` to merge the maximum available.
|
|
1305
|
+
# @param question [Integer] Question identifier
|
|
1306
|
+
# @param amount [String, Numeric, nil] Amount of shares to merge; nil = maximum available
|
|
1307
|
+
# @return [Hash] Exchange response
|
|
1308
|
+
def merge_question(question:, amount: nil)
|
|
1309
|
+
nonce = timestamp_ms
|
|
1310
|
+
action = {
|
|
1311
|
+
type: 'userOutcome',
|
|
1312
|
+
mergeQuestion: { question: question, amount: amount&.to_s }
|
|
1313
|
+
}
|
|
1314
|
+
signature = @signer.sign_l1_action(
|
|
1315
|
+
action, nonce,
|
|
1316
|
+
expires_after: @expires_after
|
|
1317
|
+
)
|
|
1318
|
+
post_action(action, signature, nonce, nil)
|
|
1319
|
+
end
|
|
1320
|
+
|
|
1321
|
+
# HIP-4: convert `amount` No shares from one outcome of a question into
|
|
1322
|
+
# `amount` Yes shares of every other outcome associated with that question
|
|
1323
|
+
# (`userOutcome` L1 action, negateOutcome variant).
|
|
1324
|
+
# @param question [Integer] Question identifier
|
|
1325
|
+
# @param outcome [Integer] Outcome identifier whose No shares are being negated
|
|
1326
|
+
# @param amount [String, Numeric] Amount of No shares to negate (UnsignedDecimal, coerced via to_s)
|
|
1327
|
+
# @return [Hash] Exchange response
|
|
1328
|
+
def negate_outcome(question:, outcome:, amount:)
|
|
1329
|
+
nonce = timestamp_ms
|
|
1330
|
+
action = {
|
|
1331
|
+
type: 'userOutcome',
|
|
1332
|
+
negateOutcome: { question: question, outcome: outcome, amount: amount.to_s }
|
|
1333
|
+
}
|
|
1334
|
+
signature = @signer.sign_l1_action(
|
|
1335
|
+
action, nonce,
|
|
1336
|
+
expires_after: @expires_after
|
|
1337
|
+
)
|
|
1338
|
+
post_action(action, signature, nonce, nil)
|
|
1339
|
+
end
|
|
1340
|
+
|
|
1341
|
+
# Finalize the link between a HyperCore spot token and an ERC-20 contract on
|
|
1342
|
+
# HyperEVM (`finalizeEvmContract` L1 action). `input` selects the verification method
|
|
1343
|
+
# and is passed through verbatim — accepts a Hash `{ create: { nonce: <int> } }` for an
|
|
1344
|
+
# EOA-deployed contract, or one of the strings `"firstStorageSlot"` / `"customStorageSlot"`
|
|
1345
|
+
# for contracts that store the finalizer address in a known storage slot.
|
|
1346
|
+
# @param token [Integer] HyperCore spot token identifier to link
|
|
1347
|
+
# @param input [Hash, String] Verification method (see above)
|
|
1348
|
+
# @return [Hash] Exchange response
|
|
1349
|
+
def finalize_evm_contract(token:, input:)
|
|
1350
|
+
nonce = timestamp_ms
|
|
1351
|
+
action = {
|
|
1352
|
+
type: 'finalizeEvmContract',
|
|
1353
|
+
token: token,
|
|
1354
|
+
input: input
|
|
1355
|
+
}
|
|
1356
|
+
signature = @signer.sign_l1_action(
|
|
1357
|
+
action, nonce,
|
|
1358
|
+
expires_after: @expires_after
|
|
1359
|
+
)
|
|
1360
|
+
post_action(action, signature, nonce, nil)
|
|
1361
|
+
end
|
|
1362
|
+
|
|
1363
|
+
# Opt in or out of spot dusting (`spotUser` L1 action).
|
|
1364
|
+
# Spot dusting is the protocol's automatic conversion of small spot balances.
|
|
1365
|
+
# Despite the generic action name, this method exclusively toggles that opt-out flag.
|
|
1366
|
+
# @param opt_out [Boolean] True to opt out of spot dusting, false to opt in
|
|
1367
|
+
# @return [Hash] Exchange response
|
|
1368
|
+
def spot_user(opt_out:)
|
|
1369
|
+
nonce = timestamp_ms
|
|
1370
|
+
action = { type: 'spotUser', toggleSpotDusting: { optOut: opt_out } }
|
|
1371
|
+
signature = @signer.sign_l1_action(
|
|
1372
|
+
action, nonce,
|
|
1373
|
+
expires_after: @expires_after
|
|
1374
|
+
)
|
|
1375
|
+
post_action(action, signature, nonce, nil)
|
|
1376
|
+
end
|
|
1377
|
+
|
|
1378
|
+
# Authorize an AQAv2 role (`authorizeAqav2Role` L1 action).
|
|
1379
|
+
# @param token [Integer] Token identifier
|
|
1380
|
+
# @param role [String] Role to authorize ("technical" or "treasury")
|
|
1381
|
+
# @return [Hash] Exchange response
|
|
1382
|
+
def authorize_aqav2_role(token:, role:)
|
|
1383
|
+
nonce = timestamp_ms
|
|
1384
|
+
action = { type: 'authorizeAqav2Role', token: token.to_i, role: role }
|
|
1385
|
+
signature = @signer.sign_l1_action(
|
|
1386
|
+
action, nonce,
|
|
1387
|
+
expires_after: @expires_after
|
|
1388
|
+
)
|
|
1389
|
+
post_action(action, signature, nonce, nil)
|
|
1390
|
+
end
|
|
1391
|
+
|
|
1113
1392
|
# Clear the asset metadata cache
|
|
1114
1393
|
# Call this if metadata has been updated
|
|
1115
1394
|
def reload_metadata!
|
data/lib/hyperliquid/info.rb
CHANGED
|
@@ -359,6 +359,13 @@ module Hyperliquid
|
|
|
359
359
|
@client.post(Constants::INFO_ENDPOINT, { type: 'gossipRootIps' })
|
|
360
360
|
end
|
|
361
361
|
|
|
362
|
+
# Retrieve gossip priority auction status (previous winners and current auctions)
|
|
363
|
+
# @return [Array] Two-element tuple: [0] array of previous-winner IPs per slot (String or nil),
|
|
364
|
+
# [1] array of current auction statuses per slot (same shape as perp_deploy_auction_status)
|
|
365
|
+
def gossip_priority_auction_status
|
|
366
|
+
@client.post(Constants::INFO_ENDPOINT, { type: 'gossipPriorityAuctionStatus' })
|
|
367
|
+
end
|
|
368
|
+
|
|
362
369
|
# Retrieve a user's legal verification status
|
|
363
370
|
# @param user [String] Wallet address
|
|
364
371
|
# @return [Hash] Keys: ipAllowed (Boolean), acceptedTerms (Boolean), userAllowed (Boolean)
|
|
@@ -508,6 +515,13 @@ module Hyperliquid
|
|
|
508
515
|
@client.post(Constants::INFO_ENDPOINT, { type: 'outcomeMeta' })
|
|
509
516
|
end
|
|
510
517
|
|
|
518
|
+
# Retrieve information about a settled outcome
|
|
519
|
+
# @param outcome [Integer] Outcome identifier
|
|
520
|
+
# @return [Hash, nil] Hash with spec, settleFraction, and details; or nil if not settled
|
|
521
|
+
def settled_outcome(outcome:)
|
|
522
|
+
@client.post(Constants::INFO_ENDPOINT, { type: 'settledOutcome', outcome: outcome.to_i })
|
|
523
|
+
end
|
|
524
|
+
|
|
511
525
|
# Retrieve a user's funding history
|
|
512
526
|
# @param user [String]
|
|
513
527
|
# @param start_time [Integer]
|
|
@@ -130,6 +130,14 @@ module Hyperliquid
|
|
|
130
130
|
]
|
|
131
131
|
}.freeze
|
|
132
132
|
|
|
133
|
+
STAKING_LINK_DISABLE_TRADING_USER_TYPES = {
|
|
134
|
+
'HyperliquidTransaction:StakingLinkDisableTradingUser': [
|
|
135
|
+
{ name: :hyperliquidChain, type: 'string' },
|
|
136
|
+
{ name: :tradingUser, type: 'address' },
|
|
137
|
+
{ name: :nonce, type: 'uint64' }
|
|
138
|
+
]
|
|
139
|
+
}.freeze
|
|
140
|
+
|
|
133
141
|
MULTI_SIG_TYPES = {
|
|
134
142
|
'HyperliquidTransaction:SendMultiSig': [
|
|
135
143
|
{ name: :hyperliquidChain, type: 'string' },
|
|
@@ -138,6 +146,31 @@ module Hyperliquid
|
|
|
138
146
|
]
|
|
139
147
|
}.freeze
|
|
140
148
|
|
|
149
|
+
USER_PORTFOLIO_MARGIN_TYPES = {
|
|
150
|
+
'HyperliquidTransaction:UserPortfolioMargin': [
|
|
151
|
+
{ name: :hyperliquidChain, type: 'string' },
|
|
152
|
+
{ name: :user, type: 'address' },
|
|
153
|
+
{ name: :enabled, type: 'bool' },
|
|
154
|
+
{ name: :nonce, type: 'uint64' }
|
|
155
|
+
]
|
|
156
|
+
}.freeze
|
|
157
|
+
|
|
158
|
+
C_DEPOSIT_TYPES = {
|
|
159
|
+
'HyperliquidTransaction:CDeposit': [
|
|
160
|
+
{ name: :hyperliquidChain, type: 'string' },
|
|
161
|
+
{ name: :wei, type: 'uint64' },
|
|
162
|
+
{ name: :nonce, type: 'uint64' }
|
|
163
|
+
]
|
|
164
|
+
}.freeze
|
|
165
|
+
|
|
166
|
+
C_WITHDRAW_TYPES = {
|
|
167
|
+
'HyperliquidTransaction:CWithdraw': [
|
|
168
|
+
{ name: :hyperliquidChain, type: 'string' },
|
|
169
|
+
{ name: :wei, type: 'uint64' },
|
|
170
|
+
{ name: :nonce, type: 'uint64' }
|
|
171
|
+
]
|
|
172
|
+
}.freeze
|
|
173
|
+
|
|
141
174
|
SEND_TO_EVM_WITH_DATA_TYPES = {
|
|
142
175
|
'HyperliquidTransaction:SendToEvmWithData': [
|
|
143
176
|
{ name: :hyperliquidChain, type: 'string' },
|
|
@@ -17,6 +17,16 @@ module Hyperliquid
|
|
|
17
17
|
module MultiSig
|
|
18
18
|
OUTER_PRIMARY_TYPE = 'HyperliquidTransaction:SendMultiSig'
|
|
19
19
|
|
|
20
|
+
# Wire-format normalization for userSetAbstraction inside multi_sig envelopes.
|
|
21
|
+
# Each co-signer's EIP-712 hash uses the human-readable abstraction string, but the
|
|
22
|
+
# L1 payload requires the single-char wire enum. Mirrors Python SDK 0.24.0's
|
|
23
|
+
# `_multi_sig_payload_action`.
|
|
24
|
+
USER_SET_ABSTRACTION_WIRE_VALUES = {
|
|
25
|
+
'disabled' => 'i',
|
|
26
|
+
'unifiedAccount' => 'u',
|
|
27
|
+
'portfolioMargin' => 'p'
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
20
30
|
# Build the outer multi-sig envelope (the action body posted to /exchange).
|
|
21
31
|
# @param inner_action [Hash] The wrapped action (any L1 or user-signed action body)
|
|
22
32
|
# @param multi_sig_user [String] Address of the multi-sig user (lowercased)
|
|
@@ -31,11 +41,26 @@ module Hyperliquid
|
|
|
31
41
|
payload: {
|
|
32
42
|
multiSigUser: multi_sig_user.downcase,
|
|
33
43
|
outerSigner: outer_signer.downcase,
|
|
34
|
-
action: inner_action
|
|
44
|
+
action: payload_action(inner_action)
|
|
35
45
|
}
|
|
36
46
|
}
|
|
37
47
|
end
|
|
38
48
|
|
|
49
|
+
# Normalize an inner action for the L1 payload. Currently only userSetAbstraction
|
|
50
|
+
# needs translation (long-form abstraction string → wire enum); all other actions
|
|
51
|
+
# pass through verbatim.
|
|
52
|
+
# @param inner_action [Hash] Inner action (symbol or string keys)
|
|
53
|
+
# @return [Hash] Possibly-normalized copy (or the original if no change needed)
|
|
54
|
+
def self.payload_action(inner_action)
|
|
55
|
+
return inner_action unless (inner_action[:type] || inner_action['type']) == 'userSetAbstraction'
|
|
56
|
+
|
|
57
|
+
key = inner_action.key?(:abstraction) ? :abstraction : 'abstraction'
|
|
58
|
+
abstraction = inner_action[key]
|
|
59
|
+
return inner_action unless USER_SET_ABSTRACTION_WIRE_VALUES.key?(abstraction)
|
|
60
|
+
|
|
61
|
+
inner_action.merge(key => USER_SET_ABSTRACTION_WIRE_VALUES[abstraction])
|
|
62
|
+
end
|
|
63
|
+
|
|
39
64
|
# Compute the multiSigActionHash that the submitter signs over.
|
|
40
65
|
# Mirrors Python's `sign_multi_sig_action`: action_hash(envelope - type, vault, nonce, expires).
|
|
41
66
|
# @param envelope [Hash] The multi-sig envelope (will have :type stripped before hashing)
|
data/lib/hyperliquid/version.rb
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Test 18: userPortfolioMargin (user-signed exchange action)
|
|
5
|
+
#
|
|
6
|
+
# Toggles cross-portfolio-margin mode for the calling wallet. Sends two actions:
|
|
7
|
+
# enable, then disable, leaving the wallet in its original state.
|
|
8
|
+
#
|
|
9
|
+
# WARNING: Toggling portfolio margin alters margining math on existing perp positions.
|
|
10
|
+
# Run only on a wallet without significant open exposure on testnet.
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_18_user_portfolio_margin.rb
|
|
14
|
+
|
|
15
|
+
require_relative 'test_helpers'
|
|
16
|
+
|
|
17
|
+
sdk = build_sdk
|
|
18
|
+
separator('TEST 18: userPortfolioMargin')
|
|
19
|
+
|
|
20
|
+
user = sdk.exchange.address
|
|
21
|
+
puts "Wallet: #{user}"
|
|
22
|
+
puts
|
|
23
|
+
|
|
24
|
+
puts 'Enabling portfolio margin...'
|
|
25
|
+
result = sdk.exchange.user_portfolio_margin(user: user, enabled: true)
|
|
26
|
+
|
|
27
|
+
# The protocol enforces a $10k account value or $5m volume threshold for portfolio
|
|
28
|
+
# margin eligibility. The agent testnet wallet typically meets neither, so this
|
|
29
|
+
# precondition failure is not an SDK bug — downgrade to warning and exit cleanly,
|
|
30
|
+
# matching the test_08 / test_11 pattern.
|
|
31
|
+
if result.is_a?(Hash) && result['status'] == 'err' &&
|
|
32
|
+
result['response'].to_s.include?('Portfolio margin requires')
|
|
33
|
+
puts red("WARNING: #{result['response']}")
|
|
34
|
+
puts ' Skipping — this is a testnet precondition, not an SDK failure.'
|
|
35
|
+
puts ' The action correctly serialized and signed (server returned a structured'
|
|
36
|
+
puts ' rejection, not a signing error).'
|
|
37
|
+
test_passed('Test 18 user_portfolio_margin')
|
|
38
|
+
exit 0
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
if api_error?(result)
|
|
42
|
+
puts red("user_portfolio_margin (enable) FAILED: #{result.inspect}")
|
|
43
|
+
test_passed('Test 18 user_portfolio_margin')
|
|
44
|
+
exit 1
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
unless result.is_a?(Hash) && result['status'] == 'ok' && result.dig('response', 'type') == 'default'
|
|
48
|
+
$test_failed = true
|
|
49
|
+
puts red("Unexpected enable response: #{result.inspect}")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
puts green("Enable OK: #{result.inspect}") unless $test_failed
|
|
53
|
+
|
|
54
|
+
wait_with_countdown(WAIT_SECONDS, 'Settling before toggle back...')
|
|
55
|
+
|
|
56
|
+
puts 'Disabling portfolio margin...'
|
|
57
|
+
result = sdk.exchange.user_portfolio_margin(user: user, enabled: false)
|
|
58
|
+
|
|
59
|
+
if api_error?(result)
|
|
60
|
+
puts red("user_portfolio_margin (disable) FAILED: #{result.inspect}")
|
|
61
|
+
test_passed('Test 18 user_portfolio_margin')
|
|
62
|
+
exit 1
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
unless result.is_a?(Hash) && result['status'] == 'ok' && result.dig('response', 'type') == 'default'
|
|
66
|
+
$test_failed = true
|
|
67
|
+
puts red("Unexpected disable response: #{result.inspect}")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
puts green("Disable OK: #{result.inspect}") unless $test_failed
|
|
71
|
+
|
|
72
|
+
test_passed('Test 18 user_portfolio_margin')
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Test 19: spotUser (L1 exchange action)
|
|
5
|
+
#
|
|
6
|
+
# Toggles spot-dusting opt-out for the calling wallet. Sends two actions:
|
|
7
|
+
# opt_out: true, then opt_out: false, leaving the wallet in its original state
|
|
8
|
+
# (spot dusting opted-in by default).
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# HYPERLIQUID_PRIVATE_KEY=0x... ruby scripts/test_19_spot_user.rb
|
|
12
|
+
|
|
13
|
+
require_relative 'test_helpers'
|
|
14
|
+
|
|
15
|
+
sdk = build_sdk
|
|
16
|
+
separator('TEST 19: spotUser (spot-dusting opt-out)')
|
|
17
|
+
|
|
18
|
+
puts "Wallet: #{sdk.exchange.address}"
|
|
19
|
+
puts
|
|
20
|
+
|
|
21
|
+
puts 'Opting out of spot dusting...'
|
|
22
|
+
result = sdk.exchange.spot_user(opt_out: true)
|
|
23
|
+
|
|
24
|
+
if api_error?(result)
|
|
25
|
+
puts red("spot_user (opt_out: true) FAILED: #{result.inspect}")
|
|
26
|
+
test_passed('Test 19 spot_user')
|
|
27
|
+
exit 1
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
unless result.is_a?(Hash) && result['status'] == 'ok' && result.dig('response', 'type') == 'default'
|
|
31
|
+
$test_failed = true
|
|
32
|
+
puts red("Unexpected opt-out response: #{result.inspect}")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
puts green("Opt-out OK: #{result.inspect}") unless $test_failed
|
|
36
|
+
|
|
37
|
+
wait_with_countdown(WAIT_SECONDS, 'Settling before toggle back...')
|
|
38
|
+
|
|
39
|
+
puts 'Opting back in to spot dusting...'
|
|
40
|
+
result = sdk.exchange.spot_user(opt_out: false)
|
|
41
|
+
|
|
42
|
+
if api_error?(result)
|
|
43
|
+
puts red("spot_user (opt_out: false) FAILED: #{result.inspect}")
|
|
44
|
+
test_passed('Test 19 spot_user')
|
|
45
|
+
exit 1
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
unless result.is_a?(Hash) && result['status'] == 'ok' && result.dig('response', 'type') == 'default'
|
|
49
|
+
$test_failed = true
|
|
50
|
+
puts red("Unexpected opt-in response: #{result.inspect}")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
puts green("Opt-in OK: #{result.inspect}") unless $test_failed
|
|
54
|
+
|
|
55
|
+
test_passed('Test 19 spot_user')
|
data/scripts/test_all.rb
CHANGED
|
@@ -35,7 +35,9 @@ SCRIPTS = [
|
|
|
35
35
|
'test_14_ws_candle.rb',
|
|
36
36
|
'test_15_explorer.rb',
|
|
37
37
|
'test_16_send_to_evm_with_data.rb',
|
|
38
|
-
'test_17_create_vault.rb'
|
|
38
|
+
'test_17_create_vault.rb',
|
|
39
|
+
'test_18_user_portfolio_margin.rb',
|
|
40
|
+
'test_19_spot_user.rb'
|
|
39
41
|
].freeze
|
|
40
42
|
|
|
41
43
|
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: 1.
|
|
4
|
+
version: 1.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- carter2099
|
|
@@ -133,6 +133,8 @@ files:
|
|
|
133
133
|
- scripts/test_15_explorer.rb
|
|
134
134
|
- scripts/test_16_send_to_evm_with_data.rb
|
|
135
135
|
- scripts/test_17_create_vault.rb
|
|
136
|
+
- scripts/test_18_user_portfolio_margin.rb
|
|
137
|
+
- scripts/test_19_spot_user.rb
|
|
136
138
|
- scripts/test_all.rb
|
|
137
139
|
- scripts/test_automated.rb
|
|
138
140
|
- scripts/test_helpers.rb
|