lighter_rust/signers/
ethereum.rs

1use crate::error::{LighterError, Result};
2use alloy::hex;
3use alloy::primitives::B256;
4use alloy::signers::{local::PrivateKeySigner, SignerSync};
5use bip39::{Language, Mnemonic};
6use serde_json::json;
7use sha3::{Digest, Keccak256};
8
9pub trait Signer: std::fmt::Debug + Send + Sync {
10    fn sign_message(&self, message: &str) -> Result<String>;
11    fn get_address(&self) -> Result<String>;
12}
13
14#[derive(Debug, Clone)]
15pub struct EthereumSigner {
16    signer: PrivateKeySigner,
17}
18
19impl EthereumSigner {
20    pub fn from_private_key(private_key: &str) -> Result<Self> {
21        let private_key = private_key.trim_start_matches("0x");
22        let signer =
23            PrivateKeySigner::from_slice(&hex::decode(private_key).map_err(|e| {
24                LighterError::Signing(format!("Invalid private key format: {}", e))
25            })?)
26            .map_err(|e| LighterError::Signing(format!("Invalid private key: {}", e)))?;
27
28        Ok(Self { signer })
29    }
30
31    pub fn from_mnemonic(mnemonic_phrase: &str, account_index: u32) -> Result<Self> {
32        use tiny_hderive::bip32::ExtendedPrivKey;
33
34        // Parse the mnemonic
35        let mnemonic = Mnemonic::parse_in(Language::English, mnemonic_phrase)
36            .map_err(|e| LighterError::Signing(format!("Invalid mnemonic: {}", e)))?;
37
38        // Convert to seed
39        let seed_bytes = mnemonic.to_seed("");
40
41        // Derive the key at the specified path (m/44'/60'/0'/0/{account_index})
42        let derivation_path = format!("m/44'/60'/0'/0/{}", account_index);
43        let derived = ExtendedPrivKey::derive(&seed_bytes, derivation_path.as_str())
44            .map_err(|e| LighterError::Signing(format!("Failed to derive key: {:?}", e)))?;
45
46        // Get the private key bytes
47        let private_key_bytes = derived.secret();
48
49        // Create Alloy signer from the derived private key
50        let signer = PrivateKeySigner::from_slice(&private_key_bytes)
51            .map_err(|e| LighterError::Signing(format!("Failed to create signer: {}", e)))?;
52
53        Ok(Self { signer })
54    }
55
56    pub fn random() -> Result<Self> {
57        // Generate a random mnemonic using entropy
58        use rand::RngCore;
59        let mut rng = rand::thread_rng();
60        let mut entropy = [0u8; 16]; // 128 bits for 12 word mnemonic
61        rng.fill_bytes(&mut entropy);
62
63        let mnemonic = Mnemonic::from_entropy(&entropy)
64            .map_err(|e| LighterError::Signing(format!("Failed to generate mnemonic: {}", e)))?;
65
66        println!("Generated mnemonic: {}", mnemonic);
67        Self::from_mnemonic(&mnemonic.to_string(), 0)
68    }
69
70    fn hash_message(&self, message: &str) -> B256 {
71        let prefix = format!("\x19Ethereum Signed Message:\n{}", message.len());
72        let mut hasher = Keccak256::new();
73        hasher.update(prefix.as_bytes());
74        hasher.update(message.as_bytes());
75        B256::from_slice(&hasher.finalize())
76    }
77}
78
79impl Signer for EthereumSigner {
80    fn sign_message(&self, message: &str) -> Result<String> {
81        let hash = self.hash_message(message);
82
83        let signature = self
84            .signer
85            .sign_hash_sync(&hash)
86            .map_err(|e| LighterError::Signing(format!("Failed to sign: {}", e)))?;
87
88        Ok(format!("0x{}", hex::encode(signature.as_bytes())))
89    }
90
91    fn get_address(&self) -> Result<String> {
92        Ok(format!("0x{:x}", self.signer.address()))
93    }
94}
95
96pub fn sign_order_payload(
97    signer: &dyn Signer,
98    symbol: &str,
99    side: &str,
100    quantity: &str,
101    price: Option<&str>,
102    nonce: u64,
103) -> Result<String> {
104    let mut payload = json!({
105        "symbol": symbol,
106        "side": side,
107        "quantity": quantity,
108        "nonce": nonce,
109    });
110
111    if let Some(p) = price {
112        payload["price"] = json!(p);
113    }
114
115    let message = serde_json::to_string(&payload)
116        .map_err(|e| LighterError::Signing(format!("Failed to serialize payload: {}", e)))?;
117
118    signer.sign_message(&message)
119}
120
121pub fn sign_cancel_payload(
122    signer: &dyn Signer,
123    order_id: Option<&str>,
124    client_order_id: Option<&str>,
125    symbol: Option<&str>,
126    nonce: u64,
127) -> Result<String> {
128    let mut payload = json!({
129        "nonce": nonce,
130    });
131
132    if let Some(id) = order_id {
133        payload["order_id"] = json!(id);
134    }
135
136    if let Some(client_id) = client_order_id {
137        payload["client_order_id"] = json!(client_id);
138    }
139
140    if let Some(sym) = symbol {
141        payload["symbol"] = json!(sym);
142    }
143
144    let message = serde_json::to_string(&payload)
145        .map_err(|e| LighterError::Signing(format!("Failed to serialize payload: {}", e)))?;
146
147    signer.sign_message(&message)
148}