lighter_rust/signers/
ethereum.rs1use 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 let mnemonic = Mnemonic::parse_in(Language::English, mnemonic_phrase)
36 .map_err(|e| LighterError::Signing(format!("Invalid mnemonic: {}", e)))?;
37
38 let seed_bytes = mnemonic.to_seed("");
40
41 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 let private_key_bytes = derived.secret();
48
49 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 use rand::RngCore;
59 let mut rng = rand::thread_rng();
60 let mut entropy = [0u8; 16]; 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}