@@ -11,7 +11,7 @@ use executor_core::intent_executor::IntentExecutor;
1111use executor_primitives:: {
1212 to_omni_auth, utils:: hex:: hex_encode, ChainId , ClientAuth , Identity , UserAuth , UserId ,
1313} ;
14- use hyperliquid_rust_sdk:: { ApproveAgent , ApproveBuilderFee , Eip712 , Withdraw3 } ;
14+ use hyperliquid_rust_sdk:: { ApproveAgent , ApproveBuilderFee , Eip712 , SendAsset , Withdraw3 } ;
1515use jsonrpsee:: { types:: ErrorObject , RpcModule } ;
1616use pumpx:: pubkey_to_address;
1717use serde:: { Deserialize , Serialize } ;
@@ -32,9 +32,26 @@ pub struct GetHyperliquidSignatureDataParams {
3232#[ derive( Debug , Deserialize , Clone ) ]
3333#[ serde( tag = "type" , rename_all = "snake_case" ) ]
3434pub enum HyperliquidActionType {
35- ApproveAgent { agent_address : String , agent_name : Option < String > } ,
36- Withdraw3 { amount : String , destination : String } ,
37- ApproveBuilderFee { max_fee_rate : String , builder : String } ,
35+ ApproveAgent {
36+ agent_address : String ,
37+ agent_name : Option < String > ,
38+ } ,
39+ Withdraw3 {
40+ amount : String ,
41+ destination : String ,
42+ } ,
43+ ApproveBuilderFee {
44+ max_fee_rate : String ,
45+ builder : String ,
46+ } ,
47+ SendAsset {
48+ destination : String ,
49+ source_dex : String ,
50+ destination_dex : String ,
51+ token : String ,
52+ amount : String ,
53+ from_sub_account : String ,
54+ } ,
3855}
3956
4057#[ derive( Serialize , Clone ) ]
@@ -56,6 +73,7 @@ pub enum HyperliquidAction {
5673 ApproveAgent ( ApproveAgent ) ,
5774 Withdraw3 ( Withdraw3 ) ,
5875 ApproveBuilderFee ( ApproveBuilderFee ) ,
76+ SendAsset ( SendAsset ) ,
5977}
6078
6179fn is_testnet_chain ( chain_id : ChainId ) -> bool {
@@ -299,6 +317,36 @@ pub fn register_get_hyperliquid_signature_data<
299317 generate_eip712_signature ( & ctx, & action, omni_account. as_ref ( ) ) . await ?;
300318 ( HyperliquidAction :: ApproveBuilderFee ( action) , signature)
301319 } ,
320+ HyperliquidActionType :: SendAsset {
321+ destination,
322+ source_dex,
323+ destination_dex,
324+ token,
325+ amount,
326+ from_sub_account,
327+ } => {
328+ let _ = validate_ethereum_address ( & destination, "destination" )
329+ . map_err ( |e| e. to_error_object ( ) ) ?;
330+ // Validate from_sub_account if it's not empty
331+ if !from_sub_account. is_empty ( ) {
332+ let _ = validate_ethereum_address ( & from_sub_account, "from_sub_account" )
333+ . map_err ( |e| e. to_error_object ( ) ) ?;
334+ }
335+ let action = SendAsset {
336+ signature_chain_id : params. chain_id ,
337+ hyperliquid_chain,
338+ destination,
339+ source_dex,
340+ destination_dex,
341+ token,
342+ amount,
343+ from_sub_account,
344+ nonce,
345+ } ;
346+ let signature =
347+ generate_eip712_signature ( & ctx, & action, omni_account. as_ref ( ) ) . await ?;
348+ ( HyperliquidAction :: SendAsset ( action) , signature)
349+ } ,
302350 } ;
303351
304352 Ok ( GetHyperliquidSignatureDataResponse {
@@ -494,6 +542,114 @@ mod tests {
494542 ) ) ;
495543 }
496544
545+ #[ test]
546+ fn test_send_asset_action_signature ( ) {
547+ let action = SendAsset {
548+ signature_chain_id : 998 ,
549+ hyperliquid_chain : "Testnet" . to_string ( ) ,
550+ destination : "0x1234567890123456789012345678901234567890" . to_string ( ) ,
551+ source_dex : "" . to_string ( ) ,
552+ destination_dex : "" . to_string ( ) ,
553+ token : "PURR:0xc4bf3f870c0e9465323c0b6ed28096c2" . to_string ( ) ,
554+ amount : "100.0" . to_string ( ) ,
555+ from_sub_account : "" . to_string ( ) ,
556+ nonce : 1234567890 ,
557+ } ;
558+
559+ // Test domain generation
560+ let domain = action. domain ( ) ;
561+ assert_eq ! ( domain. name, Some ( "HyperliquidSignTransaction" . into( ) ) ) ;
562+ assert_eq ! ( domain. version, Some ( "1" . into( ) ) ) ;
563+ assert_eq ! ( domain. chain_id, Some ( alloy:: primitives:: U256 :: from( 998 ) ) ) ;
564+
565+ // Test struct hash generation
566+ let struct_hash = action. struct_hash ( ) ;
567+ assert_eq ! ( struct_hash. len( ) , 32 ) ;
568+
569+ // Test EIP-712 signing hash generation
570+ let signing_hash = action. eip712_signing_hash ( ) ;
571+ assert_eq ! ( signing_hash. len( ) , 32 ) ;
572+ }
573+
574+ #[ test]
575+ fn test_params_deserialization_send_asset ( ) {
576+ let json = r#"{
577+ "user_id": {"type": "email", "value": "[email protected] "}, 578+ "user_auth": {"type": "email", "value": "123456"},
579+ "client_id": "test_client",
580+ "action_type": {
581+ "type": "send_asset",
582+ "destination": "0x742d35Cc6634C0532925a3b844Bc9e7595f02A10",
583+ "source_dex": "",
584+ "destination_dex": "spot",
585+ "token": "PURR:0xc4bf3f870c0e9465323c0b6ed28096c2",
586+ "amount": "50.5",
587+ "from_sub_account": ""
588+ },
589+ "chain_id": 998
590+ }"# ;
591+
592+ let params: GetHyperliquidSignatureDataParams = serde_json:: from_str ( json) . unwrap ( ) ;
593+
594+ assert ! ( matches!(
595+ params. action_type,
596+ HyperliquidActionType :: SendAsset {
597+ destination,
598+ source_dex,
599+ destination_dex,
600+ token,
601+ amount,
602+ from_sub_account
603+ }
604+ if destination == "0x742d35Cc6634C0532925a3b844Bc9e7595f02A10"
605+ && source_dex == ""
606+ && destination_dex == "spot"
607+ && token == "PURR:0xc4bf3f870c0e9465323c0b6ed28096c2"
608+ && amount == "50.5"
609+ && from_sub_account == ""
610+ ) ) ;
611+ assert_eq ! ( params. chain_id, 998 ) ;
612+ }
613+
614+ #[ test]
615+ fn test_params_deserialization_send_asset_with_sub_account ( ) {
616+ let json = r#"{
617+ "user_id": {"type": "email", "value": "[email protected] "}, 618+ "user_auth": {"type": "email", "value": "123456"},
619+ "client_id": "test_client",
620+ "action_type": {
621+ "type": "send_asset",
622+ "destination": "0x742d35Cc6634C0532925a3b844Bc9e7595f02A10",
623+ "source_dex": "hyperliquid",
624+ "destination_dex": "",
625+ "token": "USDC:0x0",
626+ "amount": "1000.0",
627+ "from_sub_account": "0x9876543210987654321098765432109876543210"
628+ },
629+ "chain_id": 998
630+ }"# ;
631+
632+ let params: GetHyperliquidSignatureDataParams = serde_json:: from_str ( json) . unwrap ( ) ;
633+
634+ assert ! ( matches!(
635+ params. action_type,
636+ HyperliquidActionType :: SendAsset {
637+ destination,
638+ source_dex,
639+ destination_dex,
640+ token,
641+ amount,
642+ from_sub_account
643+ }
644+ if destination == "0x742d35Cc6634C0532925a3b844Bc9e7595f02A10"
645+ && source_dex == "hyperliquid"
646+ && destination_dex == ""
647+ && token == "USDC:0x0"
648+ && amount == "1000.0"
649+ && from_sub_account == "0x9876543210987654321098765432109876543210"
650+ ) ) ;
651+ }
652+
497653 #[ test]
498654 fn test_is_testnet_chain ( ) {
499655 // Test mainnet chain IDs
0 commit comments