diff --git a/tee-worker/omni-executor/Cargo.lock b/tee-worker/omni-executor/Cargo.lock index 36ff10d2ee..f645821180 100644 --- a/tee-worker/omni-executor/Cargo.lock +++ b/tee-worker/omni-executor/Cargo.lock @@ -5278,7 +5278,7 @@ dependencies = [ [[package]] name = "hyperliquid_rust_sdk" version = "0.6.0" -source = "git+https://github.com/silva-fj/hyperliquid-rust-sdk?branch=eip712-public-trait#c906f75a334d35a89875015305e1e95f3add9bfb" +source = "git+https://github.com/BillyWooo/hyperliquid-rust-sdk?branch=usdClassTransfer_sendAsset#74973595c6ad81d2539356dc813592b450c7eb54" dependencies = [ "alloy", "chrono", diff --git a/tee-worker/omni-executor/Cargo.toml b/tee-worker/omni-executor/Cargo.toml index da730e7e4b..0849863d1a 100644 --- a/tee-worker/omni-executor/Cargo.toml +++ b/tee-worker/omni-executor/Cargo.toml @@ -56,7 +56,7 @@ hex = "0.4" hex-literal = "0.4" hmac = "0.12.1" http = "1.3.1" -hyperliquid_rust_sdk = { git = "https://github.com/silva-fj/hyperliquid-rust-sdk", branch = "eip712-public-trait" } +hyperliquid_rust_sdk = { git = "https://github.com/BillyWooo/hyperliquid-rust-sdk", branch = "usdClassTransfer_sendAsset" } jsonrpsee = { version = "0.24.9", features = ["server"] } jsonwebtoken = "9.3.0" libsecp256k1 = "0.7.1" diff --git a/tee-worker/omni-executor/rpc-server/src/methods/omni/get_hyperliquid_signature_data.rs b/tee-worker/omni-executor/rpc-server/src/methods/omni/get_hyperliquid_signature_data.rs index dd230a1a87..677f4b66e6 100644 --- a/tee-worker/omni-executor/rpc-server/src/methods/omni/get_hyperliquid_signature_data.rs +++ b/tee-worker/omni-executor/rpc-server/src/methods/omni/get_hyperliquid_signature_data.rs @@ -11,7 +11,7 @@ use executor_core::intent_executor::IntentExecutor; use executor_primitives::{ to_omni_auth, utils::hex::hex_encode, ChainId, ClientAuth, Identity, UserAuth, UserId, }; -use hyperliquid_rust_sdk::{ApproveAgent, ApproveBuilderFee, Eip712, Withdraw3}; +use hyperliquid_rust_sdk::{ApproveAgent, ApproveBuilderFee, Eip712, SendAsset, Withdraw3}; use jsonrpsee::{types::ErrorObject, RpcModule}; use pumpx::pubkey_to_address; use serde::{Deserialize, Serialize}; @@ -32,9 +32,26 @@ pub struct GetHyperliquidSignatureDataParams { #[derive(Debug, Deserialize, Clone)] #[serde(tag = "type", rename_all = "snake_case")] pub enum HyperliquidActionType { - ApproveAgent { agent_address: String, agent_name: Option }, - Withdraw3 { amount: String, destination: String }, - ApproveBuilderFee { max_fee_rate: String, builder: String }, + ApproveAgent { + agent_address: String, + agent_name: Option, + }, + Withdraw3 { + amount: String, + destination: String, + }, + ApproveBuilderFee { + max_fee_rate: String, + builder: String, + }, + SendAsset { + destination: String, + source_dex: String, + destination_dex: String, + token: String, + amount: String, + from_sub_account: String, + }, } #[derive(Serialize, Clone)] @@ -56,6 +73,7 @@ pub enum HyperliquidAction { ApproveAgent(ApproveAgent), Withdraw3(Withdraw3), ApproveBuilderFee(ApproveBuilderFee), + SendAsset(SendAsset), } fn is_testnet_chain(chain_id: ChainId) -> bool { @@ -299,6 +317,36 @@ pub fn register_get_hyperliquid_signature_data< generate_eip712_signature(&ctx, &action, omni_account.as_ref()).await?; (HyperliquidAction::ApproveBuilderFee(action), signature) }, + HyperliquidActionType::SendAsset { + destination, + source_dex, + destination_dex, + token, + amount, + from_sub_account, + } => { + let _ = validate_ethereum_address(&destination, "destination") + .map_err(|e| e.to_error_object())?; + // Validate from_sub_account if it's not empty + if !from_sub_account.is_empty() { + let _ = validate_ethereum_address(&from_sub_account, "from_sub_account") + .map_err(|e| e.to_error_object())?; + } + let action = SendAsset { + signature_chain_id: params.chain_id, + hyperliquid_chain, + destination, + source_dex, + destination_dex, + token, + amount, + from_sub_account, + nonce, + }; + let signature = + generate_eip712_signature(&ctx, &action, omni_account.as_ref()).await?; + (HyperliquidAction::SendAsset(action), signature) + }, }; Ok(GetHyperliquidSignatureDataResponse { @@ -494,6 +542,114 @@ mod tests { )); } + #[test] + fn test_send_asset_action_signature() { + let action = SendAsset { + signature_chain_id: 998, + hyperliquid_chain: "Testnet".to_string(), + destination: "0x1234567890123456789012345678901234567890".to_string(), + source_dex: "".to_string(), + destination_dex: "".to_string(), + token: "PURR:0xc4bf3f870c0e9465323c0b6ed28096c2".to_string(), + amount: "100.0".to_string(), + from_sub_account: "".to_string(), + nonce: 1234567890, + }; + + // Test domain generation + let domain = action.domain(); + assert_eq!(domain.name, Some("HyperliquidSignTransaction".into())); + assert_eq!(domain.version, Some("1".into())); + assert_eq!(domain.chain_id, Some(alloy::primitives::U256::from(998))); + + // Test struct hash generation + let struct_hash = action.struct_hash(); + assert_eq!(struct_hash.len(), 32); + + // Test EIP-712 signing hash generation + let signing_hash = action.eip712_signing_hash(); + assert_eq!(signing_hash.len(), 32); + } + + #[test] + fn test_params_deserialization_send_asset() { + let json = r#"{ + "user_id": {"type": "email", "value": "test@example.com"}, + "user_auth": {"type": "email", "value": "123456"}, + "client_id": "test_client", + "action_type": { + "type": "send_asset", + "destination": "0x742d35Cc6634C0532925a3b844Bc9e7595f02A10", + "source_dex": "", + "destination_dex": "spot", + "token": "PURR:0xc4bf3f870c0e9465323c0b6ed28096c2", + "amount": "50.5", + "from_sub_account": "" + }, + "chain_id": 998 + }"#; + + let params: GetHyperliquidSignatureDataParams = serde_json::from_str(json).unwrap(); + + assert!(matches!( + params.action_type, + HyperliquidActionType::SendAsset { + destination, + source_dex, + destination_dex, + token, + amount, + from_sub_account + } + if destination == "0x742d35Cc6634C0532925a3b844Bc9e7595f02A10" + && source_dex == "" + && destination_dex == "spot" + && token == "PURR:0xc4bf3f870c0e9465323c0b6ed28096c2" + && amount == "50.5" + && from_sub_account == "" + )); + assert_eq!(params.chain_id, 998); + } + + #[test] + fn test_params_deserialization_send_asset_with_sub_account() { + let json = r#"{ + "user_id": {"type": "email", "value": "test@example.com"}, + "user_auth": {"type": "email", "value": "123456"}, + "client_id": "test_client", + "action_type": { + "type": "send_asset", + "destination": "0x742d35Cc6634C0532925a3b844Bc9e7595f02A10", + "source_dex": "hyperliquid", + "destination_dex": "", + "token": "USDC:0x0", + "amount": "1000.0", + "from_sub_account": "0x9876543210987654321098765432109876543210" + }, + "chain_id": 998 + }"#; + + let params: GetHyperliquidSignatureDataParams = serde_json::from_str(json).unwrap(); + + assert!(matches!( + params.action_type, + HyperliquidActionType::SendAsset { + destination, + source_dex, + destination_dex, + token, + amount, + from_sub_account + } + if destination == "0x742d35Cc6634C0532925a3b844Bc9e7595f02A10" + && source_dex == "hyperliquid" + && destination_dex == "" + && token == "USDC:0x0" + && amount == "1000.0" + && from_sub_account == "0x9876543210987654321098765432109876543210" + )); + } + #[test] fn test_is_testnet_chain() { // Test mainnet chain IDs