hyperliquid_sdk_rs/types/
actions.rs

1use alloy::primitives::B256;
2use serde;
3
4use crate::l1_action;
5use crate::types::requests::{
6    BuilderInfo, CancelRequest, CancelRequestCloid, ModifyRequest, OrderRequest,
7};
8
9// User Actions (with HyperliquidTransaction: prefix)
10
11// UsdSend needs custom serialization for signature_chain_id
12#[derive(Debug, Clone, serde::Serialize)]
13#[serde(rename_all = "camelCase")]
14pub struct UsdSend {
15    #[serde(serialize_with = "serialize_chain_id")]
16    pub signature_chain_id: u64,
17    pub hyperliquid_chain: String,
18    pub destination: String,
19    pub amount: String,
20    pub time: u64,
21}
22
23impl crate::types::eip712::HyperliquidAction for UsdSend {
24    const TYPE_STRING: &'static str =
25        "UsdSend(string hyperliquidChain,string destination,string amount,uint64 time)";
26    const USE_PREFIX: bool = true;
27
28    fn chain_id(&self) -> Option<u64> {
29        Some(self.signature_chain_id)
30    }
31
32    fn encode_data(&self) -> Vec<u8> {
33        use crate::types::eip712::encode_value;
34        let mut encoded = Vec::new();
35        encoded.extend_from_slice(&Self::type_hash()[..]);
36        encoded.extend_from_slice(&encode_value(&self.hyperliquid_chain)[..]);
37        encoded.extend_from_slice(&encode_value(&self.destination)[..]);
38        encoded.extend_from_slice(&encode_value(&self.amount)[..]);
39        encoded.extend_from_slice(&encode_value(&self.time)[..]);
40        encoded
41    }
42}
43
44// Withdraw needs custom serialization for signature_chain_id
45#[derive(Debug, Clone, serde::Serialize)]
46#[serde(rename_all = "camelCase")]
47pub struct Withdraw {
48    #[serde(serialize_with = "serialize_chain_id")]
49    pub signature_chain_id: u64,
50    pub hyperliquid_chain: String,
51    pub destination: String,
52    pub amount: String,
53    pub time: u64,
54}
55
56impl crate::types::eip712::HyperliquidAction for Withdraw {
57    const TYPE_STRING: &'static str =
58        "Withdraw(string hyperliquidChain,string destination,string amount,uint64 time)";
59    const USE_PREFIX: bool = true;
60
61    fn chain_id(&self) -> Option<u64> {
62        Some(self.signature_chain_id)
63    }
64
65    fn encode_data(&self) -> Vec<u8> {
66        use crate::types::eip712::encode_value;
67        let mut encoded = Vec::new();
68        encoded.extend_from_slice(&Self::type_hash()[..]);
69        encoded.extend_from_slice(&encode_value(&self.hyperliquid_chain)[..]);
70        encoded.extend_from_slice(&encode_value(&self.destination)[..]);
71        encoded.extend_from_slice(&encode_value(&self.amount)[..]);
72        encoded.extend_from_slice(&encode_value(&self.time)[..]);
73        encoded
74    }
75}
76
77// SpotSend needs custom serialization for signature_chain_id
78#[derive(Debug, Clone, serde::Serialize)]
79#[serde(rename_all = "camelCase")]
80pub struct SpotSend {
81    #[serde(serialize_with = "serialize_chain_id")]
82    pub signature_chain_id: u64,
83    pub hyperliquid_chain: String,
84    pub destination: String,
85    pub token: String,
86    pub amount: String,
87    pub time: u64,
88}
89
90impl crate::types::eip712::HyperliquidAction for SpotSend {
91    const TYPE_STRING: &'static str = "SpotSend(string hyperliquidChain,string destination,string token,string amount,uint64 time)";
92    const USE_PREFIX: bool = true;
93
94    fn chain_id(&self) -> Option<u64> {
95        Some(self.signature_chain_id)
96    }
97
98    fn encode_data(&self) -> Vec<u8> {
99        use crate::types::eip712::encode_value;
100        let mut encoded = Vec::new();
101        encoded.extend_from_slice(&Self::type_hash()[..]);
102        encoded.extend_from_slice(&encode_value(&self.hyperliquid_chain)[..]);
103        encoded.extend_from_slice(&encode_value(&self.destination)[..]);
104        encoded.extend_from_slice(&encode_value(&self.token)[..]);
105        encoded.extend_from_slice(&encode_value(&self.amount)[..]);
106        encoded.extend_from_slice(&encode_value(&self.time)[..]);
107        encoded
108    }
109}
110
111// ApproveAgent needs custom serialization for the address field
112#[derive(Debug, Clone, serde::Serialize)]
113#[serde(rename_all = "camelCase")]
114pub struct ApproveAgent {
115    #[serde(serialize_with = "serialize_chain_id")]
116    pub signature_chain_id: u64,
117    pub hyperliquid_chain: String,
118    #[serde(serialize_with = "serialize_address")]
119    pub agent_address: alloy::primitives::Address,
120    pub agent_name: Option<String>,
121    pub nonce: u64,
122}
123
124pub(crate) fn serialize_address<S>(
125    address: &alloy::primitives::Address,
126    serializer: S,
127) -> Result<S::Ok, S::Error>
128where
129    S: serde::Serializer,
130{
131    serializer.serialize_str(&format!("{:#x}", address))
132}
133
134pub(crate) fn serialize_chain_id<S>(
135    chain_id: &u64,
136    serializer: S,
137) -> Result<S::Ok, S::Error>
138where
139    S: serde::Serializer,
140{
141    // Serialize as hex string to match SDK format
142    serializer.serialize_str(&format!("{:#x}", chain_id))
143}
144
145impl crate::types::eip712::HyperliquidAction for ApproveAgent {
146    const TYPE_STRING: &'static str = "ApproveAgent(string hyperliquidChain,address agentAddress,string agentName,uint64 nonce)";
147    const USE_PREFIX: bool = true;
148
149    fn chain_id(&self) -> Option<u64> {
150        Some(self.signature_chain_id)
151    }
152
153    fn encode_data(&self) -> Vec<u8> {
154        use crate::types::eip712::encode_value;
155        let mut encoded = Vec::new();
156        encoded.extend_from_slice(&Self::type_hash()[..]);
157        encoded.extend_from_slice(&encode_value(&self.hyperliquid_chain)[..]);
158        encoded.extend_from_slice(&encode_value(&self.agent_address)[..]);
159        // SDK uses unwrap_or_default() for agent_name
160        let agent_name = self.agent_name.clone().unwrap_or_default();
161        encoded.extend_from_slice(&encode_value(&agent_name)[..]);
162        encoded.extend_from_slice(&encode_value(&self.nonce)[..]);
163        encoded
164    }
165}
166
167// ApproveBuilderFee needs custom serialization for signature_chain_id
168#[derive(Debug, Clone, serde::Serialize)]
169#[serde(rename_all = "camelCase")]
170pub struct ApproveBuilderFee {
171    #[serde(serialize_with = "serialize_chain_id")]
172    pub signature_chain_id: u64,
173    pub hyperliquid_chain: String,
174    pub max_fee_rate: String,
175    pub builder: String,
176    pub nonce: u64,
177}
178
179impl crate::types::eip712::HyperliquidAction for ApproveBuilderFee {
180    const TYPE_STRING: &'static str = "ApproveBuilderFee(string hyperliquidChain,string maxFeeRate,string builder,uint64 nonce)";
181    const USE_PREFIX: bool = true;
182
183    fn chain_id(&self) -> Option<u64> {
184        Some(self.signature_chain_id)
185    }
186
187    fn encode_data(&self) -> Vec<u8> {
188        use crate::types::eip712::encode_value;
189        let mut encoded = Vec::new();
190        encoded.extend_from_slice(&Self::type_hash()[..]);
191        encoded.extend_from_slice(&encode_value(&self.hyperliquid_chain)[..]);
192        encoded.extend_from_slice(&encode_value(&self.max_fee_rate)[..]);
193        encoded.extend_from_slice(&encode_value(&self.builder)[..]);
194        encoded.extend_from_slice(&encode_value(&self.nonce)[..]);
195        encoded
196    }
197}
198
199// L1 Actions (use Exchange domain)
200
201l1_action! {
202    /// Agent connection action
203    struct Agent {
204        pub source: String,
205        pub connection_id: B256,
206    }
207    => "Agent(string source,bytes32 connectionId)"
208    => encode(source, connection_id)
209}
210
211// Exchange Actions (these don't need EIP-712 signing but are included for completeness)
212
213#[derive(Debug, Clone, serde::Serialize)]
214#[serde(rename_all = "camelCase")]
215pub struct UpdateLeverage {
216    pub asset: u32,
217    pub is_cross: bool,
218    pub leverage: u32,
219}
220
221#[derive(Debug, Clone, serde::Serialize)]
222#[serde(rename_all = "camelCase")]
223pub struct UpdateIsolatedMargin {
224    pub asset: u32,
225    pub is_buy: bool,
226    pub ntli: i64,
227}
228
229#[derive(Debug, Clone, serde::Serialize)]
230#[serde(rename_all = "camelCase")]
231pub struct VaultTransfer {
232    pub vault_address: String,
233    pub is_deposit: bool,
234    pub usd: u64,
235}
236
237#[derive(Debug, Clone, serde::Serialize)]
238#[serde(rename_all = "camelCase")]
239pub struct SpotUser {
240    pub class_transfer: ClassTransfer,
241}
242
243#[derive(Debug, Clone, serde::Serialize)]
244#[serde(rename_all = "camelCase")]
245pub struct ClassTransfer {
246    pub usd_size: u64,
247    pub to_perp: bool,
248}
249
250#[derive(Debug, Clone, serde::Serialize)]
251#[serde(rename_all = "camelCase")]
252pub struct SetReferrer {
253    pub code: String,
254}
255
256// Bulk actions that contain other types
257
258#[derive(Debug, Clone, serde::Serialize)]
259#[serde(rename_all = "camelCase")]
260pub struct BulkOrder {
261    pub orders: Vec<OrderRequest>,
262    pub grouping: String,
263    #[serde(default, skip_serializing_if = "Option::is_none")]
264    pub builder: Option<BuilderInfo>,
265}
266
267#[derive(Debug, Clone, serde::Serialize)]
268#[serde(rename_all = "camelCase")]
269pub struct BulkCancel {
270    pub cancels: Vec<CancelRequest>,
271}
272
273#[derive(Debug, Clone, serde::Serialize)]
274#[serde(rename_all = "camelCase")]
275pub struct BulkModify {
276    pub modifies: Vec<ModifyRequest>,
277}
278
279#[derive(Debug, Clone, serde::Serialize)]
280#[serde(rename_all = "camelCase")]
281pub struct BulkCancelCloid {
282    pub cancels: Vec<CancelRequestCloid>,
283}
284
285// ==================== Phase 1 New Actions ====================
286
287/// Schedule automatic order cancellation
288#[derive(Debug, Clone, serde::Serialize)]
289#[serde(rename_all = "camelCase")]
290pub struct ScheduleCancel {
291    pub time: Option<u64>,
292}
293
294/// Create a sub-account
295#[derive(Debug, Clone, serde::Serialize)]
296#[serde(rename_all = "camelCase")]
297pub struct CreateSubAccount {
298    pub name: Option<String>,
299}
300
301/// Transfer USD to/from a sub-account
302#[derive(Debug, Clone, serde::Serialize)]
303#[serde(rename_all = "camelCase")]
304pub struct SubAccountTransfer {
305    pub sub_account_user: String,
306    pub is_deposit: bool,
307    pub usd: u64,
308}
309
310/// Transfer spot tokens to/from a sub-account
311#[derive(Debug, Clone, serde::Serialize)]
312#[serde(rename_all = "camelCase")]
313pub struct SubAccountSpotTransfer {
314    pub sub_account_user: String,
315    pub is_deposit: bool,
316    pub token: String,
317    pub amount: String,
318}
319
320/// Transfer USD between perp and spot classes (different from spotUser classTransfer)
321#[derive(Debug, Clone, serde::Serialize)]
322#[serde(rename_all = "camelCase")]
323pub struct UsdClassTransfer {
324    pub amount: String,
325    pub to_perp: bool,
326}
327
328// ==================== Phase 2 New Actions ====================
329
330/// TWAP order request
331#[derive(Debug, Clone, serde::Serialize)]
332#[serde(rename_all = "camelCase")]
333pub struct TwapOrder {
334    /// Asset index
335    #[serde(rename = "a")]
336    pub asset: u32,
337    /// Is buy order
338    #[serde(rename = "b")]
339    pub is_buy: bool,
340    /// Size to execute
341    #[serde(rename = "s")]
342    pub sz: String,
343    /// Reduce only flag
344    #[serde(rename = "r")]
345    pub reduce_only: bool,
346    /// Duration in minutes
347    #[serde(rename = "m")]
348    pub duration_minutes: u32,
349    /// Randomize execution
350    #[serde(rename = "t")]
351    pub randomize: bool,
352}
353
354/// Bulk TWAP order wrapper
355#[derive(Debug, Clone, serde::Serialize)]
356#[serde(rename_all = "camelCase")]
357pub struct BulkTwapOrder {
358    pub twap: TwapOrder,
359}
360
361/// Cancel TWAP order
362#[derive(Debug, Clone, serde::Serialize)]
363#[serde(rename_all = "camelCase")]
364pub struct TwapCancel {
365    /// Asset index
366    #[serde(rename = "a")]
367    pub asset: u32,
368    /// TWAP order ID
369    #[serde(rename = "t")]
370    pub twap_id: u64,
371}
372
373/// Convert account to multi-sig user
374#[derive(Debug, Clone, serde::Serialize)]
375#[serde(rename_all = "camelCase")]
376pub struct ConvertToMultiSigUser {
377    #[serde(serialize_with = "serialize_chain_id")]
378    pub signature_chain_id: u64,
379    pub hyperliquid_chain: String,
380    /// Sorted list of authorized user addresses
381    pub signers: Vec<MultiSigSigner>,
382    /// Required number of signatures
383    pub threshold: u32,
384    pub nonce: u64,
385}
386
387/// Multi-sig signer information
388#[derive(Debug, Clone, serde::Serialize)]
389#[serde(rename_all = "camelCase")]
390pub struct MultiSigSigner {
391    pub address: String,
392    pub weight: u32,
393}
394
395impl crate::types::eip712::HyperliquidAction for ConvertToMultiSigUser {
396    const TYPE_STRING: &'static str =
397        "ConvertToMultiSigUser(string hyperliquidChain,address[] authorizedUsers,uint32 threshold,uint64 nonce)";
398    const USE_PREFIX: bool = true;
399
400    fn chain_id(&self) -> Option<u64> {
401        Some(self.signature_chain_id)
402    }
403
404    fn encode_data(&self) -> Vec<u8> {
405        use crate::types::eip712::encode_value;
406        use alloy::primitives::keccak256;
407
408        let mut encoded = Vec::new();
409        encoded.extend_from_slice(&Self::type_hash()[..]);
410        encoded.extend_from_slice(&encode_value(&self.hyperliquid_chain)[..]);
411
412        // Encode array of addresses
413        let mut addresses_encoded = Vec::new();
414        for signer in &self.signers {
415            // Parse address and encode
416            if let Ok(addr) = signer.address.parse::<alloy::primitives::Address>() {
417                addresses_encoded.extend_from_slice(&encode_value(&addr)[..]);
418            }
419        }
420        let addresses_hash = keccak256(&addresses_encoded);
421        encoded.extend_from_slice(&addresses_hash[..]);
422
423        encoded.extend_from_slice(&encode_value(&(self.threshold as u64))[..]);
424        encoded.extend_from_slice(&encode_value(&self.nonce)[..]);
425        encoded
426    }
427}
428
429/// Execute a multi-sig transaction
430#[derive(Debug, Clone, serde::Serialize)]
431#[serde(rename_all = "camelCase")]
432pub struct MultiSig {
433    #[serde(serialize_with = "serialize_chain_id")]
434    pub signature_chain_id: u64,
435    /// The multi-sig user address
436    pub multi_sig_user: String,
437    /// The outer signer (one of the authorized users)
438    pub outer_signer: String,
439    /// The inner action to execute
440    pub inner_action: serde_json::Value,
441    /// Signatures from other authorized users
442    pub signatures: Vec<MultiSigSignature>,
443    pub nonce: u64,
444}
445
446/// Signature for multi-sig transaction
447#[derive(Debug, Clone, serde::Serialize)]
448#[serde(rename_all = "camelCase")]
449pub struct MultiSigSignature {
450    pub r: String,
451    pub s: String,
452    pub v: u8,
453}
454
455/// Enable DEX abstraction for an agent
456#[derive(Debug, Clone, serde::Serialize)]
457#[serde(rename_all = "camelCase")]
458pub struct AgentEnableDexAbstraction {
459    // This action has no additional fields - just the type
460}
461
462// ==================== Phase 3 New Actions ====================
463
464// --- Spot Deployment Actions ---
465
466/// Register a new spot token
467#[derive(Debug, Clone, serde::Serialize)]
468#[serde(rename_all = "camelCase")]
469pub struct SpotDeployRegisterToken {
470    /// Token name/symbol
471    pub token_name: String,
472    /// Size decimals for trading
473    pub sz_decimals: u32,
474    /// Wei decimals for on-chain representation
475    pub wei_decimals: u32,
476    /// Maximum gas for deployment
477    pub max_gas: String,
478    /// Full name of the token
479    #[serde(skip_serializing_if = "Option::is_none")]
480    pub full_name: Option<String>,
481}
482
483/// User genesis for spot deployment
484#[derive(Debug, Clone, serde::Serialize)]
485#[serde(rename_all = "camelCase")]
486pub struct SpotDeployUserGenesis {
487    /// Token identifier
488    pub token: String,
489    /// List of (user address, wei amount) tuples for initial distribution
490    pub user_and_wei: Vec<(String, String)>,
491    /// Existing token and wei to use
492    #[serde(skip_serializing_if = "Option::is_none")]
493    pub existing_token_and_wei: Option<(String, String)>,
494}
495
496/// Freeze or unfreeze a user in spot deployment
497#[derive(Debug, Clone, serde::Serialize)]
498#[serde(rename_all = "camelCase")]
499pub struct SpotDeployFreezeUser {
500    /// Token identifier
501    pub token: String,
502    /// User address to freeze/unfreeze
503    pub user: String,
504    /// Whether to freeze (true) or unfreeze (false)
505    pub freeze: bool,
506}
507
508/// Enable freeze privilege for a token
509#[derive(Debug, Clone, serde::Serialize)]
510#[serde(rename_all = "camelCase")]
511pub struct SpotDeployEnableFreezePrivilege {
512    /// Token identifier
513    pub token: String,
514}
515
516/// Revoke freeze privilege for a token
517#[derive(Debug, Clone, serde::Serialize)]
518#[serde(rename_all = "camelCase")]
519pub struct SpotDeployRevokeFreezePrivilege {
520    /// Token identifier
521    pub token: String,
522}
523
524/// Enable quote token for spot deployment
525#[derive(Debug, Clone, serde::Serialize)]
526#[serde(rename_all = "camelCase")]
527pub struct SpotDeployEnableQuoteToken {
528    /// Token identifier to enable as quote
529    pub token: String,
530}
531
532/// Genesis for spot deployment
533#[derive(Debug, Clone, serde::Serialize)]
534#[serde(rename_all = "camelCase")]
535pub struct SpotDeployGenesis {
536    /// Token identifier
537    pub token: String,
538    /// Maximum supply
539    pub max_supply: String,
540    /// Whether to disable hyperliquidity
541    #[serde(skip_serializing_if = "Option::is_none")]
542    pub no_hyperliquidity: Option<bool>,
543}
544
545/// Register a spot trading pair
546#[derive(Debug, Clone, serde::Serialize)]
547#[serde(rename_all = "camelCase")]
548pub struct SpotDeployRegisterSpot {
549    /// Base token identifier
550    pub base_token: String,
551    /// Quote token identifier
552    pub quote_token: String,
553}
554
555/// Register hyperliquidity for a spot pair
556#[derive(Debug, Clone, serde::Serialize)]
557#[serde(rename_all = "camelCase")]
558pub struct SpotDeployRegisterHyperliquidity {
559    /// Spot pair identifier
560    pub spot: String,
561    /// Starting price
562    pub start_px: String,
563    /// Order size
564    pub order_sz: String,
565    /// Number of orders
566    pub n_orders: u32,
567    /// Number of seeded levels
568    pub n_seeded_levels: u32,
569}
570
571/// Set deployer trading fee share for a token
572#[derive(Debug, Clone, serde::Serialize)]
573#[serde(rename_all = "camelCase")]
574pub struct SpotDeploySetDeployerTradingFeeShare {
575    /// Token identifier
576    pub token: String,
577    /// Fee share (as decimal string, e.g., "0.001" for 0.1%)
578    pub share: String,
579}
580
581// --- Perp Deployment Actions ---
582
583/// Register a perpetual asset
584#[derive(Debug, Clone, serde::Serialize)]
585#[serde(rename_all = "camelCase")]
586pub struct PerpDeployRegisterAsset {
587    /// DEX identifier
588    pub dex: u32,
589    /// Maximum gas for deployment
590    pub max_gas: String,
591    /// Coin name/symbol
592    pub coin: String,
593    /// Size decimals for trading
594    pub sz_decimals: u32,
595    /// Oracle price
596    pub oracle_px: String,
597    /// Margin table ID
598    #[serde(skip_serializing_if = "Option::is_none")]
599    pub margin_table_id: Option<u32>,
600    /// Whether to use isolated margin only
601    #[serde(skip_serializing_if = "Option::is_none")]
602    pub only_isolated: Option<bool>,
603    /// Schema type
604    #[serde(skip_serializing_if = "Option::is_none")]
605    pub schema: Option<String>,
606}
607
608/// Set oracle for perpetual asset
609#[derive(Debug, Clone, serde::Serialize)]
610#[serde(rename_all = "camelCase")]
611pub struct PerpDeploySetOracle {
612    /// DEX identifier
613    pub dex: u32,
614    /// Oracle prices
615    pub oracle_pxs: Vec<String>,
616    /// All mark prices
617    pub all_mark_pxs: Vec<String>,
618    /// External perp prices
619    #[serde(skip_serializing_if = "Option::is_none")]
620    pub external_perp_pxs: Option<Vec<String>>,
621}
622
623// --- Validator/Staking Actions ---
624
625/// Unjail self (signer)
626#[derive(Debug, Clone, serde::Serialize)]
627#[serde(rename_all = "camelCase")]
628pub struct CSignerUnjailSelf {
629    // No additional fields - just the action type
630}
631
632/// Jail self (signer)
633#[derive(Debug, Clone, serde::Serialize)]
634#[serde(rename_all = "camelCase")]
635pub struct CSignerJailSelf {
636    // No additional fields - just the action type
637}
638
639/// Register as a validator
640#[derive(Debug, Clone, serde::Serialize)]
641#[serde(rename_all = "camelCase")]
642pub struct CValidatorRegister {
643    /// Node IP address
644    pub node_ip: String,
645    /// Validator name
646    pub name: String,
647    /// Validator description
648    pub description: String,
649    /// Whether delegations are disabled
650    pub delegations_disabled: bool,
651    /// Commission in basis points
652    pub commission_bps: u32,
653    /// Signer address
654    pub signer: String,
655    /// Whether initially unjailed
656    pub unjailed: bool,
657    /// Initial wei stake
658    pub initial_wei: String,
659}
660
661/// Change validator profile
662#[derive(Debug, Clone, serde::Serialize)]
663#[serde(rename_all = "camelCase")]
664pub struct CValidatorChangeProfile {
665    /// Node IP address
666    #[serde(skip_serializing_if = "Option::is_none")]
667    pub node_ip: Option<String>,
668    /// Validator name
669    #[serde(skip_serializing_if = "Option::is_none")]
670    pub name: Option<String>,
671    /// Validator description
672    #[serde(skip_serializing_if = "Option::is_none")]
673    pub description: Option<String>,
674    /// Whether unjailed
675    #[serde(skip_serializing_if = "Option::is_none")]
676    pub unjailed: Option<bool>,
677    /// Whether to disable delegations
678    #[serde(skip_serializing_if = "Option::is_none")]
679    pub disable_delegations: Option<bool>,
680    /// Commission in basis points
681    #[serde(skip_serializing_if = "Option::is_none")]
682    pub commission_bps: Option<u32>,
683    /// Signer address
684    #[serde(skip_serializing_if = "Option::is_none")]
685    pub signer: Option<String>,
686}
687
688/// Unregister as a validator
689#[derive(Debug, Clone, serde::Serialize)]
690#[serde(rename_all = "camelCase")]
691pub struct CValidatorUnregister {
692    // No additional fields - just the action type
693}
694
695/// Delegate tokens to a validator
696#[derive(Debug, Clone, serde::Serialize)]
697#[serde(rename_all = "camelCase")]
698pub struct TokenDelegate {
699    /// Validator address to delegate to
700    pub validator: String,
701    /// Amount in wei
702    pub wei: String,
703    /// Whether this is an undelegation
704    pub is_undelegate: bool,
705}
706
707// --- Other Actions ---
708
709/// Enable or disable large block mode
710#[derive(Debug, Clone, serde::Serialize)]
711#[serde(rename_all = "camelCase")]
712pub struct UseBigBlocks {
713    /// Whether to enable (true) or disable (false) big blocks
714    pub enable: bool,
715}
716
717/// No-operation action (useful for testing or keeping connection alive)
718#[derive(Debug, Clone, serde::Serialize)]
719#[serde(rename_all = "camelCase")]
720pub struct Noop {
721    /// Nonce for the action
722    pub nonce: u64,
723}
724
725// Types are now imported from requests.rs
726
727// The macros don't handle signature_chain_id, so we need to remove the duplicate trait impls
728
729#[cfg(test)]
730mod tests {
731    use alloy::primitives::keccak256;
732
733    use super::*;
734    use crate::types::eip712::HyperliquidAction;
735
736    #[test]
737    fn test_usd_send_type_hash() {
738        let expected = keccak256(
739            "HyperliquidTransaction:UsdSend(string hyperliquidChain,string destination,string amount,uint64 time)",
740        );
741        assert_eq!(UsdSend::type_hash(), expected);
742    }
743
744    #[test]
745    fn test_agent_type_hash() {
746        // L1 actions don't use the HyperliquidTransaction: prefix
747        let expected = keccak256("Agent(string source,bytes32 connectionId)");
748        assert_eq!(Agent::type_hash(), expected);
749    }
750
751    #[test]
752    fn test_agent_domain() {
753        let agent = Agent {
754            source: "a".to_string(),
755            connection_id: B256::default(),
756        };
757
758        // L1 actions use the Exchange domain
759        let domain = agent.domain();
760        let expected_domain = alloy::sol_types::eip712_domain! {
761            name: "Exchange",
762            version: "1",
763            chain_id: 1337u64,
764            verifying_contract: alloy::primitives::address!("0000000000000000000000000000000000000000"),
765        };
766
767        // Compare domain separators to verify they're the same
768        assert_eq!(domain.separator(), expected_domain.separator());
769    }
770}