hyperliquid 1.9.2 → 2.0.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 +40 -0
- data/README.md +26 -0
- data/docs/API.md +374 -152
- data/docs/CONFIGURATION.md +25 -11
- data/docs/DEVELOPMENT.md +93 -15
- data/docs/ERRORS.md +2 -0
- data/docs/EXAMPLES.md +253 -2
- data/docs/WS.md +78 -20
- data/lib/hyperliquid/client.rb +19 -10
- data/lib/hyperliquid/exchange.rb +844 -22
- data/lib/hyperliquid/info.rb +23 -11
- data/lib/hyperliquid/signing/signer.rb +18 -1
- data/lib/hyperliquid/version.rb +1 -1
- data/lib/hyperliquid/ws/client.rb +192 -78
- data/lib/hyperliquid.rb +6 -0
- metadata +16 -34
- data/.rspec +0 -3
- data/.rubocop.yml +0 -49
- data/.ruby-version +0 -1
- data/CLAUDE.md +0 -108
- data/CODE_OF_CONDUCT.md +0 -132
- data/Rakefile +0 -12
- data/example.rb +0 -161
- data/scripts/test_01_spot_market_roundtrip.rb +0 -48
- data/scripts/test_02_spot_limit_order.rb +0 -48
- data/scripts/test_03_perp_market_roundtrip.rb +0 -58
- data/scripts/test_04_perp_limit_order.rb +0 -52
- data/scripts/test_05_update_leverage.rb +0 -50
- data/scripts/test_06_modify_order.rb +0 -67
- data/scripts/test_07_market_close.rb +0 -55
- data/scripts/test_08_usd_class_transfer.rb +0 -36
- data/scripts/test_09_sub_account_lifecycle.rb +0 -51
- data/scripts/test_10_vault.rb +0 -68
- data/scripts/test_11_builder_fee.rb +0 -81
- data/scripts/test_12_staking.rb +0 -70
- data/scripts/test_13_ws_l2_book.rb +0 -83
- data/scripts/test_14_ws_candle.rb +0 -81
- data/scripts/test_15_explorer.rb +0 -71
- data/scripts/test_16_send_to_evm_with_data.rb +0 -79
- data/scripts/test_17_create_vault.rb +0 -73
- data/scripts/test_18_user_portfolio_margin.rb +0 -72
- data/scripts/test_19_spot_user.rb +0 -55
- data/scripts/test_20_explorer_ws.rb +0 -92
- data/scripts/test_all.rb +0 -96
- data/scripts/test_automated.rb +0 -82
- data/scripts/test_helpers.rb +0 -238
- data/sig/hyperliquid.rbs +0 -4
- data/test_integration.rb +0 -17
data/lib/hyperliquid/exchange.rb
CHANGED
|
@@ -34,6 +34,12 @@ module Hyperliquid
|
|
|
34
34
|
@signer.address
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
# Redacted representation: never exposes the signer's private key.
|
|
38
|
+
# @return [String]
|
|
39
|
+
def inspect
|
|
40
|
+
"#<#{self.class.name} address=#{address} testnet=#{@testnet} expires_after=#{@expires_after.inspect}>"
|
|
41
|
+
end
|
|
42
|
+
|
|
37
43
|
# Place a single order
|
|
38
44
|
# @param coin [String] Asset symbol (e.g., "BTC")
|
|
39
45
|
# @param is_buy [Boolean] True for buy, false for sell
|
|
@@ -341,7 +347,7 @@ module Hyperliquid
|
|
|
341
347
|
end
|
|
342
348
|
|
|
343
349
|
# Schedule automatic cancellation of all orders
|
|
344
|
-
# @param time [Integer, nil] UTC timestamp in milliseconds to cancel at (nil
|
|
350
|
+
# @param time [Integer, nil] UTC timestamp in milliseconds to cancel at (nil removes the scheduled cancel)
|
|
345
351
|
# @param vault_address [String, nil] Vault address for vault trading (optional)
|
|
346
352
|
# @return [Hash] Schedule cancel response
|
|
347
353
|
def schedule_cancel(time: nil, vault_address: nil)
|
|
@@ -517,7 +523,7 @@ module Hyperliquid
|
|
|
517
523
|
def create_sub_account(name:)
|
|
518
524
|
nonce = timestamp_ms
|
|
519
525
|
action = { type: 'createSubAccount', name: name }
|
|
520
|
-
signature = @signer.sign_l1_action(action, nonce)
|
|
526
|
+
signature = @signer.sign_l1_action(action, nonce, expires_after: @expires_after)
|
|
521
527
|
post_action(action, signature, nonce, nil)
|
|
522
528
|
end
|
|
523
529
|
|
|
@@ -534,7 +540,7 @@ module Hyperliquid
|
|
|
534
540
|
isDeposit: is_deposit,
|
|
535
541
|
usd: float_to_usd_int(usd)
|
|
536
542
|
}
|
|
537
|
-
signature = @signer.sign_l1_action(action, nonce)
|
|
543
|
+
signature = @signer.sign_l1_action(action, nonce, expires_after: @expires_after)
|
|
538
544
|
post_action(action, signature, nonce, nil)
|
|
539
545
|
end
|
|
540
546
|
|
|
@@ -553,7 +559,7 @@ module Hyperliquid
|
|
|
553
559
|
token: token,
|
|
554
560
|
amount: amount.to_s
|
|
555
561
|
}
|
|
556
|
-
signature = @signer.sign_l1_action(action, nonce)
|
|
562
|
+
signature = @signer.sign_l1_action(action, nonce, expires_after: @expires_after)
|
|
557
563
|
post_action(action, signature, nonce, nil)
|
|
558
564
|
end
|
|
559
565
|
|
|
@@ -570,7 +576,7 @@ module Hyperliquid
|
|
|
570
576
|
isDeposit: is_deposit,
|
|
571
577
|
usd: float_to_usd_int(usd)
|
|
572
578
|
}
|
|
573
|
-
signature = @signer.sign_l1_action(action, nonce)
|
|
579
|
+
signature = @signer.sign_l1_action(action, nonce, expires_after: @expires_after)
|
|
574
580
|
post_action(action, signature, nonce, nil)
|
|
575
581
|
end
|
|
576
582
|
|
|
@@ -580,7 +586,7 @@ module Hyperliquid
|
|
|
580
586
|
def set_referrer(code:)
|
|
581
587
|
nonce = timestamp_ms
|
|
582
588
|
action = { type: 'setReferrer', code: code }
|
|
583
|
-
signature = @signer.sign_l1_action(action, nonce)
|
|
589
|
+
signature = @signer.sign_l1_action(action, nonce, expires_after: @expires_after)
|
|
584
590
|
post_action(action, signature, nonce, nil)
|
|
585
591
|
end
|
|
586
592
|
|
|
@@ -712,7 +718,8 @@ module Hyperliquid
|
|
|
712
718
|
# Set the abstraction mode for a user (`userSetAbstraction` user-signed action).
|
|
713
719
|
# The `user` address is lowercased to match the Python SDK and protocol expectations.
|
|
714
720
|
# @param user [String] Wallet address whose abstraction is being set
|
|
715
|
-
# @param abstraction [String] One of '
|
|
721
|
+
# @param abstraction [String] One of 'unifiedAccount', 'portfolioMargin', 'disabled'
|
|
722
|
+
# (long form; the L1 agentSetAbstraction uses 'u'/'p'/'i')
|
|
716
723
|
# @return [Hash] Exchange response
|
|
717
724
|
def user_set_abstraction(user:, abstraction:)
|
|
718
725
|
nonce = timestamp_ms
|
|
@@ -1269,6 +1276,35 @@ module Hyperliquid
|
|
|
1269
1276
|
post_action(action, signature, nonce, vault_address)
|
|
1270
1277
|
end
|
|
1271
1278
|
|
|
1279
|
+
# Place a trailing stop order (`trailingStop` L1 action).
|
|
1280
|
+
# @param coin [String] Asset symbol
|
|
1281
|
+
# @param is_buy [Boolean] True for buy/long, false for sell/short
|
|
1282
|
+
# @param size [String, Numeric] Order size (base currency units)
|
|
1283
|
+
# @param reduce_only [Boolean] Reduce-only flag
|
|
1284
|
+
# @param retracement [Hash] Trailing retracement config, passed through verbatim:
|
|
1285
|
+
# { pct: "<percent String, e.g. '1.234%'>" } or { px: "<price String>" }
|
|
1286
|
+
# @param activation_px [String, nil] Activation price; nil sends null (no activation threshold)
|
|
1287
|
+
# @param vault_address [String, nil] Vault address if acting on behalf of a vault
|
|
1288
|
+
# @return [Hash] Exchange response — on success `response.data.oid`
|
|
1289
|
+
def trailing_stop(coin:, is_buy:, size:, reduce_only:, retracement:, activation_px: nil, vault_address: nil)
|
|
1290
|
+
nonce = timestamp_ms
|
|
1291
|
+
action = {
|
|
1292
|
+
type: 'trailingStop',
|
|
1293
|
+
asset: asset_index(coin),
|
|
1294
|
+
isBuy: is_buy,
|
|
1295
|
+
sz: float_to_wire(size),
|
|
1296
|
+
reduceOnly: reduce_only,
|
|
1297
|
+
retracement: retracement,
|
|
1298
|
+
activationPx: activation_px
|
|
1299
|
+
}
|
|
1300
|
+
signature = @signer.sign_l1_action(
|
|
1301
|
+
action, nonce,
|
|
1302
|
+
vault_address: vault_address,
|
|
1303
|
+
expires_after: @expires_after
|
|
1304
|
+
)
|
|
1305
|
+
post_action(action, signature, nonce, vault_address)
|
|
1306
|
+
end
|
|
1307
|
+
|
|
1272
1308
|
# Reserve additional rate-limited actions for a fee (`reserveRequestWeight` L1 action).
|
|
1273
1309
|
# @param weight [Integer] Amount of request weight to reserve
|
|
1274
1310
|
# @param destination [String, nil] Address of an existing user to reserve the weight for; nil omits the field
|
|
@@ -1360,18 +1396,130 @@ module Hyperliquid
|
|
|
1360
1396
|
post_action(action, signature, nonce, nil)
|
|
1361
1397
|
end
|
|
1362
1398
|
|
|
1363
|
-
# Activate
|
|
1364
|
-
# (`activateOutcomeDeployer` L1 action,
|
|
1365
|
-
#
|
|
1399
|
+
# Activate the signer as a HIP-4 outcome deployer and claim a venue name
|
|
1400
|
+
# (`activateOutcomeDeployer` L1 action, activate variant). Irreversible in practice:
|
|
1401
|
+
# the stake is locked for the minimum staking duration and the venue name stays
|
|
1402
|
+
# reserved forever. Requires Standard account abstraction ("disabled").
|
|
1403
|
+
# @param venue_name [String] Venue to claim (2-4 lowercase ASCII letters; server-validated)
|
|
1366
1404
|
# @return [Hash] Exchange response
|
|
1367
|
-
def activate_outcome_deployer(
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1405
|
+
def activate_outcome_deployer(venue_name:)
|
|
1406
|
+
outcome_deployer_activation({ activate: { venueName: venue_name } })
|
|
1407
|
+
end
|
|
1408
|
+
|
|
1409
|
+
# Permanently deactivate the signer as a HIP-4 outcome deployer
|
|
1410
|
+
# (`activateOutcomeDeployer` L1 action, deactivate variant). Requires the minimum
|
|
1411
|
+
# staking duration to have elapsed and no active outcomes; the account can never
|
|
1412
|
+
# activate again and its venue name stays reserved.
|
|
1413
|
+
# @return [Hash] Exchange response
|
|
1414
|
+
def deactivate_outcome_deployer
|
|
1415
|
+
outcome_deployer_activation({ deactivate: nil })
|
|
1416
|
+
end
|
|
1417
|
+
|
|
1418
|
+
# HIP-4: deploy a standalone YES/NO outcome from a standalone-outcome template
|
|
1419
|
+
# (`outcomeDeploy` L1 action, registerStandaloneOutcomeFromTemplate operation).
|
|
1420
|
+
# The response is passed through unmodified; callers must check its `status`.
|
|
1421
|
+
# @param venue [String] Deployer venue (sub-deployers pass the venue they act for)
|
|
1422
|
+
# @param template_id [String] Template id (see Info#outcome_templates)
|
|
1423
|
+
# @param keyword_to_value [Hash, Array<Array(String, String)>] One value per template
|
|
1424
|
+
# keyword; keys/values are stringified and sorted by keyword before signing
|
|
1425
|
+
# @param deployer_fee_scale [String, Numeric] Decimal in [0, 10]; Strings are sent verbatim
|
|
1426
|
+
# @return [Hash] Exchange response
|
|
1427
|
+
def register_standalone_outcome_from_template(venue:, template_id:, keyword_to_value:, deployer_fee_scale:)
|
|
1428
|
+
instance = outcome_template_instance(template_id, keyword_to_value, deployer_fee_scale)
|
|
1429
|
+
outcome_deploy_action(venue, { registerStandaloneOutcomeFromTemplate: instance })
|
|
1430
|
+
end
|
|
1431
|
+
|
|
1432
|
+
# HIP-4: deploy a question plus its named outcomes in one action
|
|
1433
|
+
# (`outcomeDeploy` L1 action, registerQuestionFromTemplate operation). The protocol
|
|
1434
|
+
# also creates the question's fallback outcome.
|
|
1435
|
+
# @param venue [String] Deployer venue
|
|
1436
|
+
# @param template_id [String] Question template id
|
|
1437
|
+
# @param keyword_to_value [Hash, Array] Question template keyword values (sorted by the SDK)
|
|
1438
|
+
# @param deployer_fee_scale [String, Numeric] Decimal in [0, 10], applies to every outcome of the question
|
|
1439
|
+
# @param named_outcomes [Array<Hash>] Each `{ template_id:, keyword_to_value: }`; order preserved
|
|
1440
|
+
# @return [Hash] Exchange response
|
|
1441
|
+
def register_question_from_template(venue:, template_id:, keyword_to_value:, deployer_fee_scale:, named_outcomes:)
|
|
1442
|
+
outcome_deploy_action(
|
|
1443
|
+
venue,
|
|
1444
|
+
{ registerQuestionFromTemplate: {
|
|
1445
|
+
questionTemplateInstance: outcome_template_instance(template_id, keyword_to_value, deployer_fee_scale),
|
|
1446
|
+
namedOutcomeTemplateInstances: named_outcomes.map do |named|
|
|
1447
|
+
outcome_template_instance(named.fetch(:template_id), named.fetch(:keyword_to_value))
|
|
1448
|
+
end
|
|
1449
|
+
} }
|
|
1450
|
+
)
|
|
1451
|
+
end
|
|
1452
|
+
|
|
1453
|
+
# HIP-4: add one named outcome to a live template-deployed question
|
|
1454
|
+
# (`outcomeDeploy` L1 action, registerAndAssociateNamedOutcomeFromTemplate operation).
|
|
1455
|
+
# @param venue [String] Deployer venue
|
|
1456
|
+
# @param question [Integer] Question identifier
|
|
1457
|
+
# @param template_id [String] Question-outcome template id (parent must be the question's template)
|
|
1458
|
+
# @param keyword_to_value [Hash, Array] Template keyword values (sorted by the SDK)
|
|
1459
|
+
# @return [Hash] Exchange response
|
|
1460
|
+
def register_and_associate_named_outcome_from_template(venue:, question:, template_id:, keyword_to_value:)
|
|
1461
|
+
outcome_deploy_action(
|
|
1462
|
+
venue,
|
|
1463
|
+
{ registerAndAssociateNamedOutcomeFromTemplate: {
|
|
1464
|
+
question: question.to_i,
|
|
1465
|
+
namedOutcomeTemplateInstance: outcome_template_instance(template_id, keyword_to_value)
|
|
1466
|
+
} }
|
|
1467
|
+
)
|
|
1468
|
+
end
|
|
1469
|
+
|
|
1470
|
+
# HIP-4: settle one outcome of the venue (`outcomeDeploy` L1 action, settleOutcome operation).
|
|
1471
|
+
# `name`, `description` and `side_names` must exactly match the outcome (copy them from
|
|
1472
|
+
# Info#outcome_meta: name, description, sideSpecs[].name).
|
|
1473
|
+
# @param venue [String] Deployer venue
|
|
1474
|
+
# @param outcome [Integer] Outcome identifier
|
|
1475
|
+
# @param settle_fraction [String, Numeric] Payout fraction of the first side in [0, 1]
|
|
1476
|
+
# @param name [String] Outcome name
|
|
1477
|
+
# @param description [String] Outcome description
|
|
1478
|
+
# @param side_names [Array<String>] The two side names
|
|
1479
|
+
# @param details [String] Settlement details (protocol currently requires '')
|
|
1480
|
+
# @return [Hash] Exchange response
|
|
1481
|
+
def settle_outcome(venue:, outcome:, settle_fraction:, name:, description:, side_names:, details: '')
|
|
1482
|
+
settlement = outcome_settlement(outcome: outcome, settle_fraction: settle_fraction, name: name,
|
|
1483
|
+
description: description, side_names: side_names, details: details)
|
|
1484
|
+
outcome_deploy_action(venue, { settleOutcome: settlement })
|
|
1485
|
+
end
|
|
1486
|
+
|
|
1487
|
+
# HIP-4: settle all remaining named outcomes of a question in one action
|
|
1488
|
+
# (`outcomeDeploy` L1 action, settleQuestion2 operation; the original settleQuestion
|
|
1489
|
+
# is discontinued). Exactly one settlement must use fraction "1", the rest "0".
|
|
1490
|
+
# @param venue [String] Deployer venue
|
|
1491
|
+
# @param question [Integer] Question identifier
|
|
1492
|
+
# @param name [String] Question name (must match outcome_meta)
|
|
1493
|
+
# @param description [String] Question description (must match outcome_meta)
|
|
1494
|
+
# @param settlements [Array<Hash>] Each `{ outcome:, settle_fraction:, name:, description:,
|
|
1495
|
+
# side_names:, details: '' }` with Symbol keys; order preserved
|
|
1496
|
+
# @return [Hash] Exchange response
|
|
1497
|
+
def settle_question(venue:, question:, name:, description:, settlements:)
|
|
1498
|
+
outcome_deploy_action(
|
|
1499
|
+
venue,
|
|
1500
|
+
{ settleQuestion2: {
|
|
1501
|
+
question: question.to_i,
|
|
1502
|
+
outcomeSettlements: settlements.map { |s| outcome_settlement(**s) },
|
|
1503
|
+
nameAndDescription: [name, description]
|
|
1504
|
+
} }
|
|
1505
|
+
)
|
|
1506
|
+
end
|
|
1507
|
+
|
|
1508
|
+
# HIP-4: grant or revoke sub-deployer permissions per operation
|
|
1509
|
+
# (`outcomeDeploy` L1 action, setSubDeployers operation).
|
|
1510
|
+
# @param venue [String] Deployer venue
|
|
1511
|
+
# @param changes [Array<Hash>] Each `{ variant:, user:, allowed: }` where variant is the camelCase
|
|
1512
|
+
# wire name (registerStandaloneOutcomeFromTemplate, registerQuestionFromTemplate,
|
|
1513
|
+
# registerAndAssociateNamedOutcomeFromTemplate, settleOutcome, or settleQuestion — which
|
|
1514
|
+
# authorizes settle_question); user is lowercased; order preserved
|
|
1515
|
+
# @return [Hash] Exchange response
|
|
1516
|
+
def set_outcome_sub_deployers(venue:, changes:)
|
|
1517
|
+
outcome_deploy_action(
|
|
1518
|
+
venue,
|
|
1519
|
+
{ setSubDeployers: changes.map do |change|
|
|
1520
|
+
{ variant: change.fetch(:variant).to_s, user: change.fetch(:user).downcase, allowed: change.fetch(:allowed) }
|
|
1521
|
+
end }
|
|
1373
1522
|
)
|
|
1374
|
-
post_action(action, signature, nonce, nil)
|
|
1375
1523
|
end
|
|
1376
1524
|
|
|
1377
1525
|
# Finalize the link between a HyperCore spot token and an ERC-20 contract on
|
|
@@ -1425,6 +1573,520 @@ module Hyperliquid
|
|
|
1425
1573
|
post_action(action, signature, nonce, nil)
|
|
1426
1574
|
end
|
|
1427
1575
|
|
|
1576
|
+
# Validator operators: jail the signer's own validator (`CSignerAction` L1 action, jailSelf variant).
|
|
1577
|
+
# Must be signed by the validator's registered signer key.
|
|
1578
|
+
# @return [Hash] Exchange response
|
|
1579
|
+
def c_signer_jail_self
|
|
1580
|
+
c_signer_action(:jailSelf)
|
|
1581
|
+
end
|
|
1582
|
+
|
|
1583
|
+
# Validator operators: unjail the signer's own validator (`CSignerAction` L1 action, unjailSelf variant).
|
|
1584
|
+
# Must be signed by the validator's registered signer key.
|
|
1585
|
+
# @return [Hash] Exchange response
|
|
1586
|
+
def c_signer_unjail_self
|
|
1587
|
+
c_signer_action(:unjailSelf)
|
|
1588
|
+
end
|
|
1589
|
+
|
|
1590
|
+
# Validator operators: vote on the risk-free rate for the aligned quote asset
|
|
1591
|
+
# (`validatorL1Stream` L1 action).
|
|
1592
|
+
# @param risk_free_rate [String, Numeric] Rate as a decimal (e.g. "0.04" for 4%); normalised via float_to_wire
|
|
1593
|
+
# @return [Hash] Exchange response
|
|
1594
|
+
def validator_l1_stream(risk_free_rate:)
|
|
1595
|
+
nonce = timestamp_ms
|
|
1596
|
+
action = { type: 'validatorL1Stream', riskFreeRate: float_to_wire(risk_free_rate) }
|
|
1597
|
+
signature = @signer.sign_l1_action(
|
|
1598
|
+
action, nonce,
|
|
1599
|
+
expires_after: @expires_after
|
|
1600
|
+
)
|
|
1601
|
+
post_action(action, signature, nonce, nil)
|
|
1602
|
+
end
|
|
1603
|
+
|
|
1604
|
+
# Validator operators: register a new validator (`CValidatorAction` L1 action, register variant).
|
|
1605
|
+
# Signed by the validator (owner) key. Wire keys are protocol-literal snake_case.
|
|
1606
|
+
# @param node_ip [String] Validator node IP address (sent as { Ip: node_ip })
|
|
1607
|
+
# @param name [String] Validator name
|
|
1608
|
+
# @param description [String] Validator description
|
|
1609
|
+
# @param delegations_disabled [Boolean] Whether delegations are disabled
|
|
1610
|
+
# @param commission_bps [Integer] Commission in basis points
|
|
1611
|
+
# @param signer [String] Signer address (lowercased)
|
|
1612
|
+
# @param unjailed [Boolean] Initial jail status (true = unjailed)
|
|
1613
|
+
# @param initial_wei [Integer] Initial self-stake in wei
|
|
1614
|
+
# @return [Hash] Exchange response
|
|
1615
|
+
def c_validator_register(node_ip:, name:, description:, delegations_disabled:, commission_bps:,
|
|
1616
|
+
signer:, unjailed:, initial_wei:)
|
|
1617
|
+
c_validator_action(
|
|
1618
|
+
register: {
|
|
1619
|
+
profile: {
|
|
1620
|
+
node_ip: { Ip: node_ip },
|
|
1621
|
+
name: name,
|
|
1622
|
+
description: description,
|
|
1623
|
+
delegations_disabled: delegations_disabled,
|
|
1624
|
+
commission_bps: Integer(commission_bps),
|
|
1625
|
+
signer: signer.downcase
|
|
1626
|
+
},
|
|
1627
|
+
unjailed: unjailed,
|
|
1628
|
+
initial_wei: Integer(initial_wei)
|
|
1629
|
+
}
|
|
1630
|
+
)
|
|
1631
|
+
end
|
|
1632
|
+
|
|
1633
|
+
# Validator operators: change the validator profile (`CValidatorAction` L1 action, changeProfile variant).
|
|
1634
|
+
# Every field is always sent; nil means "leave unchanged" (sent as JSON null).
|
|
1635
|
+
# @param unjailed [Boolean] Desired jail status (required)
|
|
1636
|
+
# @param node_ip [String, nil] New node IP (sent as { Ip: node_ip }) or nil
|
|
1637
|
+
# @param name [String, nil] New name or nil
|
|
1638
|
+
# @param description [String, nil] New description or nil
|
|
1639
|
+
# @param disable_delegations [Boolean, nil] Enable/disable delegations, or nil
|
|
1640
|
+
# @param commission_bps [Integer, nil] New commission in basis points, or nil
|
|
1641
|
+
# @param signer [String, nil] New signer address (lowercased), or nil
|
|
1642
|
+
# @return [Hash] Exchange response
|
|
1643
|
+
def c_validator_change_profile(unjailed:, node_ip: nil, name: nil, description: nil,
|
|
1644
|
+
disable_delegations: nil, commission_bps: nil, signer: nil)
|
|
1645
|
+
c_validator_action(
|
|
1646
|
+
changeProfile: {
|
|
1647
|
+
node_ip: node_ip.nil? ? nil : { Ip: node_ip },
|
|
1648
|
+
name: name,
|
|
1649
|
+
description: description,
|
|
1650
|
+
unjailed: unjailed,
|
|
1651
|
+
disable_delegations: disable_delegations,
|
|
1652
|
+
commission_bps: commission_bps.nil? ? nil : Integer(commission_bps),
|
|
1653
|
+
signer: signer&.downcase
|
|
1654
|
+
}
|
|
1655
|
+
)
|
|
1656
|
+
end
|
|
1657
|
+
|
|
1658
|
+
# Validator operators: unregister the validator (`CValidatorAction` L1 action, unregister variant).
|
|
1659
|
+
# @return [Hash] Exchange response
|
|
1660
|
+
def c_validator_unregister
|
|
1661
|
+
c_validator_action(unregister: nil)
|
|
1662
|
+
end
|
|
1663
|
+
|
|
1664
|
+
# --- HIP-3 perp deployer actions (perpDeploy L1 action) ---
|
|
1665
|
+
# All are L1-signed `perpDeploy` actions; none accept vault_address; expires_after propagates.
|
|
1666
|
+
# Coins are full HIP-3 names ("<dex>:<COIN>"). Decimal args accept String (sent verbatim)
|
|
1667
|
+
# or Numeric (normalized via float_to_wire). Integer args are Integer()-coerced and never scaled.
|
|
1668
|
+
|
|
1669
|
+
# Register a new asset on a HIP-3 perp dex (legacy `registerAsset` variant, Python SDK parity).
|
|
1670
|
+
# Passing `schema:` also creates the dex (first registration). `max_gas: nil` bids the current
|
|
1671
|
+
# deploy-auction price; `max_gas: 0` uses a reserve deployment (check perp_deploy_auction_status first).
|
|
1672
|
+
# @param dex [String] Perp dex name (2-4 lowercase chars)
|
|
1673
|
+
# @param coin [String] Full asset name, e.g. "test:TEST0"
|
|
1674
|
+
# @param sz_decimals [Integer] Size decimals
|
|
1675
|
+
# @param oracle_px [String, Numeric] Initial oracle price
|
|
1676
|
+
# @param margin_table_id [Integer] Margin table id
|
|
1677
|
+
# @param only_isolated [Boolean] Whether the asset is isolated-margin only
|
|
1678
|
+
# @param max_gas [Integer, nil] Max gas in native-token wei; nil = current auction price
|
|
1679
|
+
# @param schema [Hash, nil] New-dex schema:
|
|
1680
|
+
# { full_name:, collateral_token:, oracle_updater: nil, is_star: (optional) }
|
|
1681
|
+
# @return [Hash] Exchange response
|
|
1682
|
+
def perp_deploy_register_asset(dex:, coin:, sz_decimals:, oracle_px:, margin_table_id:, only_isolated:,
|
|
1683
|
+
max_gas: nil, schema: nil)
|
|
1684
|
+
asset_request = perp_deploy_asset_request(coin, sz_decimals, oracle_px, margin_table_id)
|
|
1685
|
+
asset_request[:onlyIsolated] = only_isolated
|
|
1686
|
+
perp_deploy_action(:registerAsset, perp_deploy_register_payload(asset_request, dex, max_gas, schema))
|
|
1687
|
+
end
|
|
1688
|
+
|
|
1689
|
+
# Register a new asset with an explicit margin mode (`registerAsset2` variant).
|
|
1690
|
+
# @param margin_mode [String] "strictIsolated", "noCross", or "normal"
|
|
1691
|
+
# (other params as perp_deploy_register_asset)
|
|
1692
|
+
# @return [Hash] Exchange response
|
|
1693
|
+
def perp_deploy_register_asset2(dex:, coin:, sz_decimals:, oracle_px:, margin_table_id:, margin_mode:,
|
|
1694
|
+
max_gas: nil, schema: nil)
|
|
1695
|
+
asset_request = perp_deploy_asset_request(coin, sz_decimals, oracle_px, margin_table_id)
|
|
1696
|
+
asset_request[:marginMode] = margin_mode
|
|
1697
|
+
perp_deploy_action(:registerAsset2, perp_deploy_register_payload(asset_request, dex, max_gas, schema))
|
|
1698
|
+
end
|
|
1699
|
+
|
|
1700
|
+
# Push oracle, mark, and external perp prices (`setOracle` variant; ≥2.5s between calls).
|
|
1701
|
+
# @param dex [String] Perp dex name
|
|
1702
|
+
# @param oracle_pxs [Hash{String=>String,Numeric}] coin => oracle price
|
|
1703
|
+
# @param all_mark_pxs [Array<Hash{String=>String,Numeric}>] 0-2 hashes of coin => mark price
|
|
1704
|
+
# @param external_perp_pxs [Hash{String=>String,Numeric}] coin => external perp price (must include all assets)
|
|
1705
|
+
# @return [Hash] Exchange response
|
|
1706
|
+
def perp_deploy_set_oracle(dex:, oracle_pxs:, all_mark_pxs:, external_perp_pxs:)
|
|
1707
|
+
perp_deploy_action(:setOracle, {
|
|
1708
|
+
dex: dex,
|
|
1709
|
+
oraclePxs: sorted_coin_pairs(oracle_pxs) { |px| perp_deploy_decimal(px) },
|
|
1710
|
+
markPxs: all_mark_pxs.map { |pxs| sorted_coin_pairs(pxs) { |px| perp_deploy_decimal(px) } },
|
|
1711
|
+
externalPerpPxs: sorted_coin_pairs(external_perp_pxs) { |px| perp_deploy_decimal(px) }
|
|
1712
|
+
})
|
|
1713
|
+
end
|
|
1714
|
+
|
|
1715
|
+
# Set per-asset funding multipliers (`setFundingMultipliers` variant).
|
|
1716
|
+
# @param multipliers [Hash{String=>String,Numeric}] coin => funding multiplier (0-10)
|
|
1717
|
+
# @return [Hash] Exchange response
|
|
1718
|
+
def perp_deploy_set_funding_multipliers(multipliers:)
|
|
1719
|
+
perp_deploy_action(:setFundingMultipliers, sorted_coin_pairs(multipliers) { |v| perp_deploy_decimal(v) })
|
|
1720
|
+
end
|
|
1721
|
+
|
|
1722
|
+
# Set per-asset funding interest rates (`setFundingInterestRates` variant).
|
|
1723
|
+
# @param rates [Hash{String=>String,Numeric}] coin => 8h interest rate (-0.01..0.01)
|
|
1724
|
+
# @return [Hash] Exchange response
|
|
1725
|
+
def perp_deploy_set_funding_interest_rates(rates:)
|
|
1726
|
+
perp_deploy_action(:setFundingInterestRates, sorted_coin_pairs(rates) { |v| perp_deploy_decimal(v) })
|
|
1727
|
+
end
|
|
1728
|
+
|
|
1729
|
+
# Set per-asset funding clamps (`setFundingClamps` variant).
|
|
1730
|
+
# @param clamps [Hash{String=>String,Numeric}] coin => 8h funding clamp (0..0.01; default 0.0003)
|
|
1731
|
+
# @return [Hash] Exchange response
|
|
1732
|
+
def perp_deploy_set_funding_clamps(clamps:)
|
|
1733
|
+
perp_deploy_action(:setFundingClamps, sorted_coin_pairs(clamps) { |v| perp_deploy_decimal(v) })
|
|
1734
|
+
end
|
|
1735
|
+
|
|
1736
|
+
# Halt or resume trading on a HIP-3 asset (`haltTrading` variant).
|
|
1737
|
+
# @param coin [String] Full asset name
|
|
1738
|
+
# @param is_halted [Boolean] true halts trading, false resumes
|
|
1739
|
+
# @return [Hash] Exchange response
|
|
1740
|
+
def perp_deploy_halt_trading(coin:, is_halted:)
|
|
1741
|
+
perp_deploy_action(:haltTrading, { coin: coin, isHalted: is_halted })
|
|
1742
|
+
end
|
|
1743
|
+
|
|
1744
|
+
# Insert a margin table on a HIP-3 perp dex (`insertMarginTable` variant).
|
|
1745
|
+
# Integer() coercion: Floats truncate; Strings use Ruby literal prefixes ('0x10' == 16).
|
|
1746
|
+
# @param dex [String] Perp dex name
|
|
1747
|
+
# @param description [String] Margin table description
|
|
1748
|
+
# @param margin_tiers [Array<Hash>] ≤3 tiers { lower_bound: Integer (1e-6 collateral units), max_leverage: 1..50 },
|
|
1749
|
+
# increasing lower_bound / decreasing max_leverage; order preserved
|
|
1750
|
+
# @return [Hash] Exchange response
|
|
1751
|
+
def perp_deploy_insert_margin_table(dex:, description:, margin_tiers:)
|
|
1752
|
+
tiers = margin_tiers.map do |tier|
|
|
1753
|
+
{ lowerBound: Integer(tier.fetch(:lower_bound)), maxLeverage: Integer(tier.fetch(:max_leverage)) }
|
|
1754
|
+
end
|
|
1755
|
+
perp_deploy_action(:insertMarginTable,
|
|
1756
|
+
{ dex: dex, marginTable: { description: description, marginTiers: tiers } })
|
|
1757
|
+
end
|
|
1758
|
+
|
|
1759
|
+
# Assign margin tables to assets (`setMarginTableIds` variant).
|
|
1760
|
+
# @param margin_table_ids [Hash{String=>Integer}] coin => non-zero margin table id
|
|
1761
|
+
# @return [Hash] Exchange response
|
|
1762
|
+
def perp_deploy_set_margin_table_ids(margin_table_ids:)
|
|
1763
|
+
perp_deploy_action(:setMarginTableIds, sorted_coin_pairs(margin_table_ids) { |id| Integer(id) })
|
|
1764
|
+
end
|
|
1765
|
+
|
|
1766
|
+
# Set per-asset margin modes (`setMarginModes` variant); modes are passed through verbatim.
|
|
1767
|
+
# @param margin_modes [Hash{String=>String}] coin => "strictIsolated" | "noCross" | "normal"
|
|
1768
|
+
# @return [Hash] Exchange response
|
|
1769
|
+
def perp_deploy_set_margin_modes(margin_modes:)
|
|
1770
|
+
perp_deploy_action(:setMarginModes, sorted_coin_pairs(margin_modes) { |mode| mode })
|
|
1771
|
+
end
|
|
1772
|
+
|
|
1773
|
+
# Set per-asset open interest caps (`setOpenInterestCaps` variant; caps ≥ 1_000_000).
|
|
1774
|
+
# @param caps [Hash{String=>Integer,nil}] coin => OI cap notional in 1e-6 collateral units;
|
|
1775
|
+
# nil removes the custom cap
|
|
1776
|
+
# @return [Hash] Exchange response
|
|
1777
|
+
def perp_deploy_set_open_interest_caps(caps:)
|
|
1778
|
+
perp_deploy_action(:setOpenInterestCaps, sorted_coin_pairs(caps) { |cap| cap.nil? ? nil : Integer(cap) })
|
|
1779
|
+
end
|
|
1780
|
+
|
|
1781
|
+
# Set the fee recipient of a HIP-3 perp dex (`setFeeRecipient` variant).
|
|
1782
|
+
# @param dex [String] Perp dex name
|
|
1783
|
+
# @param fee_recipient [String] Fee recipient address (lowercased)
|
|
1784
|
+
# @return [Hash] Exchange response
|
|
1785
|
+
def perp_deploy_set_fee_recipient(dex:, fee_recipient:)
|
|
1786
|
+
perp_deploy_action(:setFeeRecipient, { dex: dex, feeRecipient: fee_recipient.downcase })
|
|
1787
|
+
end
|
|
1788
|
+
|
|
1789
|
+
# Per-asset deployer fee share + growth mode (`setDeployerFees`; mainnet: one change per 30 days).
|
|
1790
|
+
# @param fees [Hash{String=>Hash}] coin => { scale: String|Numeric, growth_mode: Boolean };
|
|
1791
|
+
# scale 0..3, or <10 with growth mode
|
|
1792
|
+
# @return [Hash] Exchange response
|
|
1793
|
+
def perp_deploy_set_deployer_fees(fees:)
|
|
1794
|
+
perp_deploy_action(:setDeployerFees, sorted_coin_pairs(fees) do |fee|
|
|
1795
|
+
{ scale: perp_deploy_decimal(fee.fetch(:scale)), growthMode: fee.fetch(:growth_mode) }
|
|
1796
|
+
end)
|
|
1797
|
+
end
|
|
1798
|
+
|
|
1799
|
+
# Grant or revoke sub-deployer permissions (`setSubDeployers` variant); entry order is preserved.
|
|
1800
|
+
# @param dex [String] Perp dex name
|
|
1801
|
+
# @param sub_deployers [Array<Hash>] { variant:, user:, allowed: Boolean }; variant is a String
|
|
1802
|
+
# (e.g. "setOracle") or a Hash (e.g. { hip3Star: "order" }) passed through verbatim; user is lowercased
|
|
1803
|
+
# @return [Hash] Exchange response
|
|
1804
|
+
def perp_deploy_set_sub_deployers(dex:, sub_deployers:)
|
|
1805
|
+
entries = sub_deployers.map do |entry|
|
|
1806
|
+
{ variant: entry.fetch(:variant), user: entry.fetch(:user).downcase, allowed: entry.fetch(:allowed) }
|
|
1807
|
+
end
|
|
1808
|
+
perp_deploy_action(:setSubDeployers, { dex: dex, subDeployers: entries })
|
|
1809
|
+
end
|
|
1810
|
+
|
|
1811
|
+
# Set an asset's annotation (`setPerpAnnotation` variant).
|
|
1812
|
+
# @param coin [String] Full asset name
|
|
1813
|
+
# @param category [String] ≤15 chars
|
|
1814
|
+
# @param description [String] ≤400 chars
|
|
1815
|
+
# @param display_name [String, nil] ≤9 chars; nil sends null
|
|
1816
|
+
# @param keywords [Array<String>] ≤2 keywords, each ≤10 chars
|
|
1817
|
+
# @return [Hash] Exchange response
|
|
1818
|
+
def perp_deploy_set_perp_annotation(coin:, category:, description:, display_name: nil, keywords: [])
|
|
1819
|
+
perp_deploy_action(:setPerpAnnotation, {
|
|
1820
|
+
coin: coin, category: category, description: description,
|
|
1821
|
+
displayName: display_name, keywords: keywords
|
|
1822
|
+
})
|
|
1823
|
+
end
|
|
1824
|
+
|
|
1825
|
+
# Disable (shut down) a HIP-3 perp dex (`disableDex` variant).
|
|
1826
|
+
# @param dex [String] Name of the perp dex to disable
|
|
1827
|
+
# @return [Hash] Exchange response
|
|
1828
|
+
def perp_deploy_disable_dex(dex:)
|
|
1829
|
+
perp_deploy_action(:disableDex, dex)
|
|
1830
|
+
end
|
|
1831
|
+
|
|
1832
|
+
# ---- Spot deploy (HIP-1 / HIP-2): one `spotDeploy` L1 action, one method per variant ----
|
|
1833
|
+
|
|
1834
|
+
# Register a new spot token via the deploy gas auction (`spotDeploy.registerToken2`, L1).
|
|
1835
|
+
# On success `response.data` is the new token index.
|
|
1836
|
+
# @param token_name [String] Token ticker
|
|
1837
|
+
# @param sz_decimals [Integer] Size decimals
|
|
1838
|
+
# @param wei_decimals [Integer] Wei decimals
|
|
1839
|
+
# @param max_gas [Integer] Maximum auction gas the deployer will pay (HYPE wei units)
|
|
1840
|
+
# @param full_name [String, nil] Optional full name (omitted when nil)
|
|
1841
|
+
# Integer params accept an Integer or an integer String (coerced with Integer(); Floats are truncated).
|
|
1842
|
+
# @return [Hash] Exchange response
|
|
1843
|
+
def spot_deploy_register_token(token_name:, sz_decimals:, wei_decimals:, max_gas:, full_name: nil)
|
|
1844
|
+
register_token = {
|
|
1845
|
+
spec: { name: token_name, szDecimals: Integer(sz_decimals), weiDecimals: Integer(wei_decimals) },
|
|
1846
|
+
maxGas: Integer(max_gas)
|
|
1847
|
+
}
|
|
1848
|
+
register_token[:fullName] = full_name unless full_name.nil?
|
|
1849
|
+
spot_deploy_action(:registerToken2, register_token)
|
|
1850
|
+
end
|
|
1851
|
+
|
|
1852
|
+
# Assign genesis balances for a deployed token (`spotDeploy.userGenesis`, L1). Repeatable.
|
|
1853
|
+
# @param token [Integer] Token index
|
|
1854
|
+
# @param user_and_wei [Array<Array(String, Integer|String)>] [[address, wei], ...]
|
|
1855
|
+
# @param existing_token_and_wei [Array<Array(Integer, Integer|String)>] [[token, wei], ...]
|
|
1856
|
+
# @param blacklist_users [Array<Array(String, Boolean)>, nil] Omitted when nil. The server rejects it
|
|
1857
|
+
# (even []) unless both other lists are empty.
|
|
1858
|
+
# @return [Hash] Exchange response
|
|
1859
|
+
def spot_deploy_user_genesis(token:, user_and_wei:, existing_token_and_wei:, blacklist_users: nil)
|
|
1860
|
+
user_genesis = {
|
|
1861
|
+
token: Integer(token),
|
|
1862
|
+
userAndWei: user_and_wei.map { |user, wei| [user.downcase, wei_to_wire(wei)] },
|
|
1863
|
+
existingTokenAndWei: existing_token_and_wei.map { |existing, wei| [Integer(existing), wei_to_wire(wei)] }
|
|
1864
|
+
}
|
|
1865
|
+
unless blacklist_users.nil?
|
|
1866
|
+
user_genesis[:blacklistUsers] = blacklist_users.map { |user, blacklist| [user.downcase, blacklist] }
|
|
1867
|
+
end
|
|
1868
|
+
spot_deploy_action(:userGenesis, user_genesis)
|
|
1869
|
+
end
|
|
1870
|
+
|
|
1871
|
+
# Finalize genesis for a deployed token (`spotDeploy.genesis`, L1).
|
|
1872
|
+
# @param token [Integer] Token index
|
|
1873
|
+
# @param max_supply [Integer, String] Maximum supply in wei (Float raises ArgumentError)
|
|
1874
|
+
# @param no_hyperliquidity [Boolean] Send `noHyperliquidity: true` (omitted when false)
|
|
1875
|
+
# @return [Hash] Exchange response
|
|
1876
|
+
def spot_deploy_genesis(token:, max_supply:, no_hyperliquidity: false)
|
|
1877
|
+
genesis = { token: Integer(token), maxSupply: wei_to_wire(max_supply) }
|
|
1878
|
+
genesis[:noHyperliquidity] = true if no_hyperliquidity
|
|
1879
|
+
spot_deploy_action(:genesis, genesis)
|
|
1880
|
+
end
|
|
1881
|
+
|
|
1882
|
+
# Register a spot pair (`spotDeploy.registerSpot`, L1).
|
|
1883
|
+
# On success `response.data` is the new spot (pair) index.
|
|
1884
|
+
# @param base_token [Integer] Base token index
|
|
1885
|
+
# @param quote_token [Integer] Quote token index
|
|
1886
|
+
# @return [Hash] Exchange response
|
|
1887
|
+
def spot_deploy_register_spot(base_token:, quote_token:)
|
|
1888
|
+
spot_deploy_action(:registerSpot, { tokens: [Integer(base_token), Integer(quote_token)] })
|
|
1889
|
+
end
|
|
1890
|
+
|
|
1891
|
+
# Seed Hyperliquidity for a spot pair (`spotDeploy.registerHyperliquidity`, L1).
|
|
1892
|
+
# @param spot [Integer] Spot (pair) index
|
|
1893
|
+
# @param start_px [Float, String] Starting price
|
|
1894
|
+
# @param order_sz [Float, String] Size of each order
|
|
1895
|
+
# @param n_orders [Integer] Number of orders
|
|
1896
|
+
# @param n_seeded_levels [Integer, nil] Number of seeded levels (omitted when nil)
|
|
1897
|
+
# @return [Hash] Exchange response
|
|
1898
|
+
def spot_deploy_register_hyperliquidity(spot:, start_px:, order_sz:, n_orders:, n_seeded_levels: nil)
|
|
1899
|
+
register = {
|
|
1900
|
+
spot: Integer(spot),
|
|
1901
|
+
startPx: float_to_wire(start_px),
|
|
1902
|
+
orderSz: float_to_wire(order_sz),
|
|
1903
|
+
nOrders: Integer(n_orders)
|
|
1904
|
+
}
|
|
1905
|
+
register[:nSeededLevels] = Integer(n_seeded_levels) unless n_seeded_levels.nil?
|
|
1906
|
+
spot_deploy_action(:registerHyperliquidity, register)
|
|
1907
|
+
end
|
|
1908
|
+
|
|
1909
|
+
# Set the deployer trading fee share (`spotDeploy.setDeployerTradingFeeShare`, L1).
|
|
1910
|
+
# @param token [Integer] Token index
|
|
1911
|
+
# @param share [String] Percent string, e.g. "0.012%" or "100%" (may only decrease)
|
|
1912
|
+
# @return [Hash] Exchange response
|
|
1913
|
+
def spot_deploy_set_deployer_trading_fee_share(token:, share:)
|
|
1914
|
+
spot_deploy_action(:setDeployerTradingFeeShare, { token: Integer(token), share: share })
|
|
1915
|
+
end
|
|
1916
|
+
|
|
1917
|
+
# Enable the freeze privilege (`spotDeploy.enableFreezePrivilege`, L1).
|
|
1918
|
+
# Must be sent before genesis (server: "Genesis error: genesis already happened").
|
|
1919
|
+
# @param token [Integer] Token index
|
|
1920
|
+
# @return [Hash] Exchange response
|
|
1921
|
+
def spot_deploy_enable_freeze_privilege(token:)
|
|
1922
|
+
spot_deploy_action(:enableFreezePrivilege, { token: Integer(token) })
|
|
1923
|
+
end
|
|
1924
|
+
|
|
1925
|
+
# Freeze or unfreeze a user's balance of the token (`spotDeploy.freezeUser`, L1).
|
|
1926
|
+
# @param token [Integer] Token index
|
|
1927
|
+
# @param user [String] User address (lowercased)
|
|
1928
|
+
# @param freeze [Boolean] true to freeze, false to unfreeze
|
|
1929
|
+
# @return [Hash] Exchange response
|
|
1930
|
+
def spot_deploy_freeze_user(token:, user:, freeze:)
|
|
1931
|
+
spot_deploy_action(:freezeUser, { token: Integer(token), user: user.downcase, freeze: freeze })
|
|
1932
|
+
end
|
|
1933
|
+
|
|
1934
|
+
# Permanently give up the freeze privilege (`spotDeploy.revokeFreezePrivilege`, L1).
|
|
1935
|
+
# @param token [Integer] Token index
|
|
1936
|
+
# @return [Hash] Exchange response
|
|
1937
|
+
def spot_deploy_revoke_freeze_privilege(token:)
|
|
1938
|
+
spot_deploy_action(:revokeFreezePrivilege, { token: Integer(token) })
|
|
1939
|
+
end
|
|
1940
|
+
|
|
1941
|
+
# Make the token a permissionless quote token (`spotDeploy.enableQuoteToken`, L1).
|
|
1942
|
+
# Irreversible per protocol docs; requires zero deployer fee share and quote-token staking.
|
|
1943
|
+
# @param token [Integer] Token index
|
|
1944
|
+
# @return [Hash] Exchange response
|
|
1945
|
+
def spot_deploy_enable_quote_token(token:)
|
|
1946
|
+
spot_deploy_action(:enableQuoteToken, { token: Integer(token) })
|
|
1947
|
+
end
|
|
1948
|
+
|
|
1949
|
+
# Disable the token as a quote token (`spotDeploy.disableQuoteToken`, L1).
|
|
1950
|
+
# @param token [Integer] Token index
|
|
1951
|
+
# @return [Hash] Exchange response
|
|
1952
|
+
def spot_deploy_disable_quote_token(token:)
|
|
1953
|
+
spot_deploy_action(:disableQuoteToken, { token: Integer(token) })
|
|
1954
|
+
end
|
|
1955
|
+
|
|
1956
|
+
# Request linking a Core spot token to an ERC-20 on HyperEVM (`spotDeploy.requestEvmContract`, L1);
|
|
1957
|
+
# finalize with #finalize_evm_contract.
|
|
1958
|
+
# @param token [Integer] Token index
|
|
1959
|
+
# @param address [String] ERC-20 contract address (lowercased)
|
|
1960
|
+
# @param evm_extra_wei_decimals [Integer] EVM wei decimals minus Core wei decimals, in [-2, 18]
|
|
1961
|
+
# @return [Hash] Exchange response
|
|
1962
|
+
def spot_deploy_request_evm_contract(token:, address:, evm_extra_wei_decimals:)
|
|
1963
|
+
request = {
|
|
1964
|
+
token: Integer(token),
|
|
1965
|
+
address: address.downcase,
|
|
1966
|
+
evmExtraWeiDecimals: Integer(evm_extra_wei_decimals)
|
|
1967
|
+
}
|
|
1968
|
+
spot_deploy_action(:requestEvmContract, request)
|
|
1969
|
+
end
|
|
1970
|
+
|
|
1971
|
+
# Set the token annotation (`spotDeploy.setTokenAnnotation`, L1). Changeable at most once per day.
|
|
1972
|
+
# @param token [Integer] Token index
|
|
1973
|
+
# @param category [String] Category
|
|
1974
|
+
# @param description [String] Description
|
|
1975
|
+
# @param keywords [Array<String>] Keywords
|
|
1976
|
+
# @param display_name [String, nil] Display name; nil is sent as JSON null
|
|
1977
|
+
# @return [Hash] Exchange response
|
|
1978
|
+
def spot_deploy_set_token_annotation(token:, category:, description:, keywords:, display_name: nil)
|
|
1979
|
+
annotation = { category: category, description: description, displayName: display_name, keywords: keywords }
|
|
1980
|
+
spot_deploy_action(:setTokenAnnotation, { token: Integer(token), annotation: annotation })
|
|
1981
|
+
end
|
|
1982
|
+
|
|
1983
|
+
# Set the deployer label (`spotDeploy.setDeployerLabel`, L1).
|
|
1984
|
+
# Settable once per deployer; 2-4 lowercase chars, unique across deployers and perp dexs.
|
|
1985
|
+
# @param label [String] Label
|
|
1986
|
+
# @return [Hash] Exchange response
|
|
1987
|
+
def spot_deploy_set_deployer_label(label:)
|
|
1988
|
+
spot_deploy_action(:setDeployerLabel, { label: label })
|
|
1989
|
+
end
|
|
1990
|
+
|
|
1991
|
+
# HIP-3* (testnet-only) star operations — perpDeploy action, star variant
|
|
1992
|
+
|
|
1993
|
+
# Add (true) or remove (false) a user on a HIP-3* venue's allow-list
|
|
1994
|
+
# (`perpDeploy` L1 action, star proxy modifyApproval). Testnet-only.
|
|
1995
|
+
# @param dex [String] HIP-3* perp dex name
|
|
1996
|
+
# @param user [String] Proxied user address (lowercased)
|
|
1997
|
+
# @param approved [Boolean] true adds the user, false removes them and clears their flags
|
|
1998
|
+
# @return [Hash] Exchange response
|
|
1999
|
+
def star_modify_approval(dex:, user:, approved:)
|
|
2000
|
+
star_proxy_action(dex, user, { modifyApproval: approved })
|
|
2001
|
+
end
|
|
2002
|
+
|
|
2003
|
+
# Allow or disallow a user to deposit to/withdraw from a HIP-3* venue's backstop liquidator
|
|
2004
|
+
# (`perpDeploy` L1 action, star proxy modifyBackstopLiquidatorApproval). Testnet-only.
|
|
2005
|
+
# @param dex [String] HIP-3* perp dex name
|
|
2006
|
+
# @param user [String] Proxied user address (lowercased)
|
|
2007
|
+
# @param allowed [Boolean] Allow the user to deposit to/withdraw from the venue's backstop liquidator
|
|
2008
|
+
# @return [Hash] Exchange response
|
|
2009
|
+
def star_modify_backstop_liquidator_approval(dex:, user:, allowed:)
|
|
2010
|
+
star_proxy_action(dex, user, { modifyBackstopLiquidatorApproval: allowed })
|
|
2011
|
+
end
|
|
2012
|
+
|
|
2013
|
+
# Set or clear reduce-only mode for an approved user on a HIP-3* venue
|
|
2014
|
+
# (`perpDeploy` L1 action, star proxy setReduceOnly). Testnet-only.
|
|
2015
|
+
# @param dex [String] HIP-3* perp dex name
|
|
2016
|
+
# @param user [String] Proxied user address (lowercased)
|
|
2017
|
+
# @param reduce_only [Boolean] Restrict an approved user to reducing positions on the venue
|
|
2018
|
+
# @return [Hash] Exchange response
|
|
2019
|
+
def star_set_reduce_only(dex:, user:, reduce_only:)
|
|
2020
|
+
star_proxy_action(dex, user, { setReduceOnly: reduce_only })
|
|
2021
|
+
end
|
|
2022
|
+
|
|
2023
|
+
# Cancel a user's resting orders on a HIP-3* venue
|
|
2024
|
+
# (`perpDeploy` L1 action, star proxy cancel). Testnet-only.
|
|
2025
|
+
# @param dex [String] HIP-3* perp dex name
|
|
2026
|
+
# @param user [String] Proxied user address (lowercased)
|
|
2027
|
+
# @param cancels [Array<Hash>] Array of { coin:, oid: } (the user's resting orders)
|
|
2028
|
+
# @return [Hash] Exchange response
|
|
2029
|
+
def star_cancel(dex:, user:, cancels:)
|
|
2030
|
+
cancel_wires = cancels.map { |c| { a: asset_index(c[:coin]), o: c[:oid] } }
|
|
2031
|
+
star_proxy_action(dex, user, { cancel: { cancels: cancel_wires } })
|
|
2032
|
+
end
|
|
2033
|
+
|
|
2034
|
+
# Cancel all of a user's orders and TWAPs on a HIP-3* venue, optionally per coin
|
|
2035
|
+
# (`perpDeploy` L1 action, star proxy cancelAll). Testnet-only.
|
|
2036
|
+
# @param dex [String] HIP-3* perp dex name
|
|
2037
|
+
# @param user [String] Proxied user address (lowercased)
|
|
2038
|
+
# @param coins [Array<String>, nil] Coins to cancel orders/TWAPs for (1-10); nil cancels all (sends null)
|
|
2039
|
+
# @return [Hash] Exchange response
|
|
2040
|
+
def star_cancel_all(dex:, user:, coins: nil)
|
|
2041
|
+
assets = coins&.map { |coin| asset_index(coin) }
|
|
2042
|
+
star_proxy_action(dex, user, { cancelAll: { assets: assets } })
|
|
2043
|
+
end
|
|
2044
|
+
|
|
2045
|
+
# Place orders on behalf of a user on a HIP-3* venue
|
|
2046
|
+
# (`perpDeploy` L1 action, star proxy order). Testnet-only.
|
|
2047
|
+
# @param dex [String] HIP-3* perp dex name
|
|
2048
|
+
# @param user [String] Proxied user address (lowercased)
|
|
2049
|
+
# @param orders [Array<Hash>] bulk_orders-shaped hashes (:coin, :is_buy, :size, :limit_px,
|
|
2050
|
+
# :order_type, :reduce_only, :cloid); :reduce_only defaults to true (server requires reduce-only)
|
|
2051
|
+
# @param grouping [String] Order grouping (default "na")
|
|
2052
|
+
# @return [Hash] Exchange response
|
|
2053
|
+
def star_order(dex:, user:, orders:, grouping: 'na')
|
|
2054
|
+
order_wires = orders.map do |o|
|
|
2055
|
+
build_order_wire(
|
|
2056
|
+
coin: o[:coin],
|
|
2057
|
+
is_buy: o[:is_buy],
|
|
2058
|
+
size: o[:size],
|
|
2059
|
+
limit_px: o[:limit_px],
|
|
2060
|
+
order_type: o[:order_type] || { limit: { tif: 'Gtc' } },
|
|
2061
|
+
reduce_only: o.fetch(:reduce_only, true),
|
|
2062
|
+
cloid: o[:cloid]
|
|
2063
|
+
)
|
|
2064
|
+
end
|
|
2065
|
+
star_proxy_action(dex, user, { order: { orders: order_wires, grouping: grouping } })
|
|
2066
|
+
end
|
|
2067
|
+
|
|
2068
|
+
# Send collateral from a user to another address on the same HIP-3* venue
|
|
2069
|
+
# (`perpDeploy` L1 action, star proxy sendAsset). Testnet-only.
|
|
2070
|
+
# @param dex [String] HIP-3* perp dex name
|
|
2071
|
+
# @param user [String] Proxied (sending) user address (lowercased)
|
|
2072
|
+
# @param destination [String] Recipient address on the same venue (lowercased)
|
|
2073
|
+
# @param amount [String, Numeric] Collateral amount; Strings sent verbatim, Numerics via float_to_wire
|
|
2074
|
+
# (perp_deploy_decimal)
|
|
2075
|
+
# @return [Hash] Exchange response
|
|
2076
|
+
def star_send_asset(dex:, user:, destination:, amount:)
|
|
2077
|
+
send_asset = { destination: destination.downcase, amount: perp_deploy_decimal(amount) }
|
|
2078
|
+
star_proxy_action(dex, user, { sendAsset: send_asset })
|
|
2079
|
+
end
|
|
2080
|
+
|
|
2081
|
+
# Set HIP-3* spot oracle prices (`perpDeploy` L1 action, star setOracle). Testnet-only.
|
|
2082
|
+
# @param dex [String] HIP-3* perp dex name
|
|
2083
|
+
# @param oracle_pxs [Hash{String=>String,Numeric}] Dex-prefixed coin => price; sorted by coin.
|
|
2084
|
+
# Strings are sent verbatim, Numerics normalized via float_to_wire (perp_deploy_decimal)
|
|
2085
|
+
# @return [Hash] Exchange response
|
|
2086
|
+
def star_set_oracle(dex:, oracle_pxs:)
|
|
2087
|
+
star_action(dex, { setOracle: { oraclePxs: sorted_coin_pairs(oracle_pxs) { |px| perp_deploy_decimal(px) } } })
|
|
2088
|
+
end
|
|
2089
|
+
|
|
1428
2090
|
# Clear the asset metadata cache
|
|
1429
2091
|
# Call this if metadata has been updated
|
|
1430
2092
|
def reload_metadata!
|
|
@@ -1449,7 +2111,7 @@ module Hyperliquid
|
|
|
1449
2111
|
|
|
1450
2112
|
# Get current timestamp in milliseconds
|
|
1451
2113
|
def timestamp_ms
|
|
1452
|
-
(
|
|
2114
|
+
Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
|
|
1453
2115
|
end
|
|
1454
2116
|
|
|
1455
2117
|
# Find a position for a coin
|
|
@@ -1527,9 +2189,17 @@ module Hyperliquid
|
|
|
1527
2189
|
end
|
|
1528
2190
|
end
|
|
1529
2191
|
|
|
2192
|
+
# spotMeta carries szDecimals on tokens[], not on universe[] pairs; a pair's
|
|
2193
|
+
# size decimals are its base token's (pair['tokens'][0], matched by token index).
|
|
1530
2194
|
def load_spot_assets
|
|
1531
|
-
@info.spot_meta
|
|
1532
|
-
|
|
2195
|
+
spot_meta = @info.spot_meta
|
|
2196
|
+
tokens_by_index = spot_meta['tokens'].to_h { |token| [token['index'], token] }
|
|
2197
|
+
spot_meta['universe'].each_with_index do |pair, index|
|
|
2198
|
+
base_index = pair['tokens'][0]
|
|
2199
|
+
base_token = tokens_by_index[base_index]
|
|
2200
|
+
raise Error, "spotMeta pair #{pair['name']} references unknown base token index #{base_index}" unless base_token
|
|
2201
|
+
|
|
2202
|
+
cache_asset(pair['name'], index + SPOT_ASSET_THRESHOLD, base_token['szDecimals'], is_spot: true)
|
|
1533
2203
|
end
|
|
1534
2204
|
end
|
|
1535
2205
|
|
|
@@ -1584,6 +2254,64 @@ module Hyperliquid
|
|
|
1584
2254
|
normalized.sub(/(\.\d*?)0+\z/, '\1').sub(/\.\z/, '')
|
|
1585
2255
|
end
|
|
1586
2256
|
|
|
2257
|
+
# Sign and post a HIP-4 `outcomeDeploy` L1 action. Key order type, venue, operation is load-bearing.
|
|
2258
|
+
def outcome_deploy_action(venue, operation)
|
|
2259
|
+
nonce = timestamp_ms
|
|
2260
|
+
action = { type: 'outcomeDeploy', venue: venue, operation: operation }
|
|
2261
|
+
signature = @signer.sign_l1_action(action, nonce, expires_after: @expires_after)
|
|
2262
|
+
post_action(action, signature, nonce, nil)
|
|
2263
|
+
end
|
|
2264
|
+
|
|
2265
|
+
# Sign and post an `activateOutcomeDeployer` L1 action with the given enum variant.
|
|
2266
|
+
def outcome_deployer_activation(variant)
|
|
2267
|
+
nonce = timestamp_ms
|
|
2268
|
+
action = { type: 'activateOutcomeDeployer' }.merge(variant)
|
|
2269
|
+
signature = @signer.sign_l1_action(action, nonce, expires_after: @expires_after)
|
|
2270
|
+
post_action(action, signature, nonce, nil)
|
|
2271
|
+
end
|
|
2272
|
+
|
|
2273
|
+
# Template instance { id, keywordToValue[, deployerFeeScale] } (fee scale only on
|
|
2274
|
+
# standalone/question instances, never on named-outcome instances).
|
|
2275
|
+
def outcome_template_instance(template_id, keyword_to_value, deployer_fee_scale = nil)
|
|
2276
|
+
instance = { id: template_id, keywordToValue: outcome_keyword_pairs(keyword_to_value) }
|
|
2277
|
+
instance[:deployerFeeScale] = outcome_decimal_wire(deployer_fee_scale) unless deployer_fee_scale.nil?
|
|
2278
|
+
instance
|
|
2279
|
+
end
|
|
2280
|
+
|
|
2281
|
+
# Hash or [[k, v], ...] -> [[String, String], ...] sorted by keyword (signed list must be sorted).
|
|
2282
|
+
def outcome_keyword_pairs(keyword_to_value)
|
|
2283
|
+
keyword_to_value.to_a.map { |keyword, value| [keyword.to_s, value.to_s] }.sort_by(&:first)
|
|
2284
|
+
end
|
|
2285
|
+
|
|
2286
|
+
# Decimal string for HIP-4 fee scales / settle fractions: Strings verbatim, Numerics via float_to_wire.
|
|
2287
|
+
def outcome_decimal_wire(value)
|
|
2288
|
+
value.is_a?(String) ? value : float_to_wire(value)
|
|
2289
|
+
end
|
|
2290
|
+
|
|
2291
|
+
# One settlement entry; key order outcome, settleFraction, details, nameAndDescription, sideNames.
|
|
2292
|
+
def outcome_settlement(outcome:, settle_fraction:, name:, description:, side_names:, details: '')
|
|
2293
|
+
{
|
|
2294
|
+
outcome: outcome.to_i,
|
|
2295
|
+
settleFraction: outcome_decimal_wire(settle_fraction),
|
|
2296
|
+
details: details,
|
|
2297
|
+
nameAndDescription: [name, description],
|
|
2298
|
+
sideNames: side_names.map(&:to_s)
|
|
2299
|
+
}
|
|
2300
|
+
end
|
|
2301
|
+
|
|
2302
|
+
# Convert an integer wei amount to its wire string. Integers are stringified exactly and
|
|
2303
|
+
# Strings pass through verbatim. Floats are rejected (Float#to_s gives "1.0e+16" / "100.0" forms).
|
|
2304
|
+
# @param value [Integer, String] Amount in wei
|
|
2305
|
+
# @return [String]
|
|
2306
|
+
# @raise [ArgumentError] For any other type
|
|
2307
|
+
def wei_to_wire(value)
|
|
2308
|
+
case value
|
|
2309
|
+
when Integer then value.to_s
|
|
2310
|
+
when String then value
|
|
2311
|
+
else raise ArgumentError, "wei amount must be an Integer or String, got #{value.inspect}"
|
|
2312
|
+
end
|
|
2313
|
+
end
|
|
2314
|
+
|
|
1587
2315
|
# Calculate slippage price for market orders
|
|
1588
2316
|
# Maintains parity with official Python SDK
|
|
1589
2317
|
# 1. Apply slippage to mid price
|
|
@@ -1611,8 +2339,9 @@ module Hyperliquid
|
|
|
1611
2339
|
base_decimals = is_spot ? 8 : 6
|
|
1612
2340
|
decimal_places = [base_decimals - sz_decimals, 0].max
|
|
1613
2341
|
|
|
1614
|
-
# Round to decimal places
|
|
1615
|
-
|
|
2342
|
+
# Round to decimal places like Python's round(): correctly rounded on the exact binary value, ties to
|
|
2343
|
+
# even (Float#round rounds ties half-up and misrounds e.g. 2.675 -> 2.68)
|
|
2344
|
+
rounded = sig_figs_price.to_r.round(decimal_places, half: :even).to_f
|
|
1616
2345
|
|
|
1617
2346
|
# Format with fixed decimal places
|
|
1618
2347
|
format("%.#{decimal_places}f", rounded)
|
|
@@ -1669,6 +2398,62 @@ module Hyperliquid
|
|
|
1669
2398
|
{ b: builder[:b].downcase, f: builder[:f] }
|
|
1670
2399
|
end
|
|
1671
2400
|
|
|
2401
|
+
# Sign and post a `perpDeploy` L1 action: { type: 'perpDeploy', <variant_key> => payload }.
|
|
2402
|
+
def perp_deploy_action(variant_key, payload)
|
|
2403
|
+
nonce = timestamp_ms
|
|
2404
|
+
action = { type: 'perpDeploy', variant_key => payload }
|
|
2405
|
+
signature = @signer.sign_l1_action(action, nonce, expires_after: @expires_after)
|
|
2406
|
+
post_action(action, signature, nonce, nil)
|
|
2407
|
+
end
|
|
2408
|
+
|
|
2409
|
+
def perp_deploy_asset_request(coin, sz_decimals, oracle_px, margin_table_id)
|
|
2410
|
+
{ coin: coin, szDecimals: Integer(sz_decimals), oraclePx: perp_deploy_decimal(oracle_px),
|
|
2411
|
+
marginTableId: Integer(margin_table_id) }
|
|
2412
|
+
end
|
|
2413
|
+
|
|
2414
|
+
# Key order maxGas, assetRequest, dex, schema; nil maxGas/schema are sent as null.
|
|
2415
|
+
def perp_deploy_register_payload(asset_request, dex, max_gas, schema)
|
|
2416
|
+
{ maxGas: max_gas.nil? ? nil : Integer(max_gas), assetRequest: asset_request, dex: dex,
|
|
2417
|
+
schema: perp_dex_schema_wire(schema) }
|
|
2418
|
+
end
|
|
2419
|
+
|
|
2420
|
+
def perp_dex_schema_wire(schema)
|
|
2421
|
+
return nil if schema.nil?
|
|
2422
|
+
|
|
2423
|
+
wire = {
|
|
2424
|
+
fullName: schema.fetch(:full_name),
|
|
2425
|
+
collateralToken: Integer(schema.fetch(:collateral_token)),
|
|
2426
|
+
oracleUpdater: schema[:oracle_updater]&.downcase
|
|
2427
|
+
}
|
|
2428
|
+
wire[:isStar] = schema[:is_star] if schema.key?(:is_star)
|
|
2429
|
+
wire
|
|
2430
|
+
end
|
|
2431
|
+
|
|
2432
|
+
# Hash (or [coin, value] pairs) -> [[coin, converted], ...] sorted by coin.
|
|
2433
|
+
def sorted_coin_pairs(pairs)
|
|
2434
|
+
pairs.to_h.map { |coin, value| [coin.to_s, yield(value)] }.sort_by(&:first)
|
|
2435
|
+
end
|
|
2436
|
+
|
|
2437
|
+
# String sent verbatim (Python parity); Numeric normalized via float_to_wire.
|
|
2438
|
+
def perp_deploy_decimal(value)
|
|
2439
|
+
case value
|
|
2440
|
+
when String then value
|
|
2441
|
+
when Numeric then float_to_wire(value)
|
|
2442
|
+
else raise ArgumentError, "decimal must be String or Numeric. Got: #{value.class}"
|
|
2443
|
+
end
|
|
2444
|
+
end
|
|
2445
|
+
|
|
2446
|
+
# HIP-3* star operation envelope: { type: 'perpDeploy', star: { dex:, operation: } }
|
|
2447
|
+
def star_action(dex, operation)
|
|
2448
|
+
perp_deploy_action(:star, { dex: dex, operation: operation })
|
|
2449
|
+
end
|
|
2450
|
+
|
|
2451
|
+
# HIP-3* proxied user operation: operation { proxy: [user, proxy_operation] }
|
|
2452
|
+
def star_proxy_action(dex, user, proxy_operation)
|
|
2453
|
+
star_action(dex, { proxy: [user.downcase, proxy_operation] })
|
|
2454
|
+
end
|
|
2455
|
+
|
|
2456
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1672
2457
|
# Convert order type to wire format
|
|
1673
2458
|
# @param order_type [Hash] Order type configuration
|
|
1674
2459
|
# @return [Hash] Wire format order type
|
|
@@ -1696,6 +2481,43 @@ module Hyperliquid
|
|
|
1696
2481
|
raise ArgumentError, 'order_type must specify :limit or :trigger'
|
|
1697
2482
|
end
|
|
1698
2483
|
end
|
|
2484
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
2485
|
+
|
|
2486
|
+
# Build, sign, and post a `CSignerAction` L1 action whose variant key carries null.
|
|
2487
|
+
# @param variant [Symbol] :jailSelf or :unjailSelf
|
|
2488
|
+
def c_signer_action(variant)
|
|
2489
|
+
nonce = timestamp_ms
|
|
2490
|
+
action = { type: 'CSignerAction', variant => nil }
|
|
2491
|
+
signature = @signer.sign_l1_action(
|
|
2492
|
+
action, nonce,
|
|
2493
|
+
expires_after: @expires_after
|
|
2494
|
+
)
|
|
2495
|
+
post_action(action, signature, nonce, nil)
|
|
2496
|
+
end
|
|
2497
|
+
|
|
2498
|
+
# Build, sign, and post a `CValidatorAction` L1 action. `type` is always the first key.
|
|
2499
|
+
# @param variant_body [Hash] Exactly one of { register: {...} }, { changeProfile: {...} }, { unregister: nil }
|
|
2500
|
+
def c_validator_action(variant_body)
|
|
2501
|
+
nonce = timestamp_ms
|
|
2502
|
+
action = { type: 'CValidatorAction' }.merge(variant_body)
|
|
2503
|
+
signature = @signer.sign_l1_action(
|
|
2504
|
+
action, nonce,
|
|
2505
|
+
expires_after: @expires_after
|
|
2506
|
+
)
|
|
2507
|
+
post_action(action, signature, nonce, nil)
|
|
2508
|
+
end
|
|
2509
|
+
|
|
2510
|
+
# Build, sign (L1), and post a `spotDeploy` action with a single variant key.
|
|
2511
|
+
# `type` must be inserted first: msgpack preserves Hash order and the server hashes it.
|
|
2512
|
+
# @param variant [Symbol] Variant key, e.g. :registerToken2
|
|
2513
|
+
# @param payload [Hash, Array] Variant payload
|
|
2514
|
+
# @return [Hash] Exchange response
|
|
2515
|
+
def spot_deploy_action(variant, payload)
|
|
2516
|
+
nonce = timestamp_ms
|
|
2517
|
+
action = { type: 'spotDeploy', variant => payload }
|
|
2518
|
+
signature = @signer.sign_l1_action(action, nonce, expires_after: @expires_after)
|
|
2519
|
+
post_action(action, signature, nonce, nil)
|
|
2520
|
+
end
|
|
1699
2521
|
|
|
1700
2522
|
# Post an action to the exchange endpoint
|
|
1701
2523
|
def post_action(action, signature, nonce, vault_address)
|