hyperliquid_sdk_rs/types/
eip712.rs

1use alloy::primitives::{keccak256, Address, B256, U256};
2use alloy::sol_types::Eip712Domain;
3
4pub trait HyperliquidAction: Sized + serde::Serialize {
5    /// The EIP-712 type string (without HyperliquidTransaction: prefix)
6    const TYPE_STRING: &'static str;
7
8    /// Whether this uses the HyperliquidTransaction: prefix
9    const USE_PREFIX: bool = true;
10
11    /// Get chain ID for domain construction
12    /// Override this method for actions with signature_chain_id
13    fn chain_id(&self) -> Option<u64> {
14        None
15    }
16
17    /// Get the EIP-712 domain for this action
18    fn domain(&self) -> Eip712Domain {
19        let chain_id = self.chain_id().unwrap_or(1); // Default to mainnet
20        alloy::sol_types::eip712_domain! {
21            name: "HyperliquidSignTransaction",
22            version: "1",
23            chain_id: chain_id,
24            verifying_contract: alloy::primitives::address!("0000000000000000000000000000000000000000"),
25        }
26    }
27
28    fn type_hash() -> B256 {
29        let type_string = if Self::USE_PREFIX {
30            format!("HyperliquidTransaction:{}", Self::TYPE_STRING)
31        } else {
32            Self::TYPE_STRING.to_string()
33        };
34        keccak256(type_string.as_bytes())
35    }
36
37    /// Encode the struct data according to EIP-712 rules
38    /// Default implementation - should be overridden for proper field ordering
39    fn encode_data(&self) -> Vec<u8> {
40        // This is a placeholder - each action should implement proper encoding
41        // based on its TYPE_STRING field order
42        let mut encoded = Vec::new();
43        encoded.extend_from_slice(&Self::type_hash()[..]);
44        // Subclasses should implement the rest
45        encoded
46    }
47
48    fn struct_hash(&self) -> B256 {
49        keccak256(self.encode_data())
50    }
51
52    fn eip712_signing_hash(&self, domain: &Eip712Domain) -> B256 {
53        let domain_separator = domain.separator();
54        let struct_hash = self.struct_hash();
55
56        let mut buf = Vec::with_capacity(66);
57        buf.push(0x19);
58        buf.push(0x01);
59        buf.extend_from_slice(&domain_separator[..]);
60        buf.extend_from_slice(&struct_hash[..]);
61
62        keccak256(&buf)
63    }
64}
65
66/// Encode a value according to EIP-712 rules
67pub fn encode_value<T: EncodeEip712>(value: &T) -> [u8; 32] {
68    value.encode_eip712()
69}
70
71/// Trait for types that can be encoded in EIP-712
72pub trait EncodeEip712 {
73    fn encode_eip712(&self) -> [u8; 32];
74}
75
76impl EncodeEip712 for String {
77    fn encode_eip712(&self) -> [u8; 32] {
78        keccak256(self.as_bytes()).into()
79    }
80}
81
82impl EncodeEip712 for u64 {
83    fn encode_eip712(&self) -> [u8; 32] {
84        U256::from(*self).to_be_bytes::<32>()
85    }
86}
87
88impl EncodeEip712 for B256 {
89    fn encode_eip712(&self) -> [u8; 32] {
90        (*self).into()
91    }
92}
93
94impl EncodeEip712 for Address {
95    fn encode_eip712(&self) -> [u8; 32] {
96        let mut result = [0u8; 32];
97        result[12..].copy_from_slice(self.as_slice());
98        result
99    }
100}
101
102impl<T: EncodeEip712> EncodeEip712 for Option<T> {
103    fn encode_eip712(&self) -> [u8; 32] {
104        match self {
105            Some(v) => v.encode_eip712(),
106            None => keccak256("".as_bytes()).into(), // Empty string hash for None
107        }
108    }
109}