clob_client_rust/
signer_adapter.rs1use crate::errors::ClobError;
2use crate::signing::Eip712Signer;
3use alloy::primitives::{Address, U256};
4use alloy::signers::Signer;
5use alloy::signers::local::PrivateKeySigner;
6use alloy::sol;
7use alloy::sol_types::{Eip712Domain, SolStruct};
8use async_trait::async_trait;
9use std::str::FromStr;
10use std::sync::Arc;
11
12sol! {
14 #[derive(serde::Deserialize, Debug)]
15 struct ClobAuth {
16 address address;
17 string timestamp;
18 uint256 nonce;
19 string message;
20 }
21
22 #[derive(serde::Deserialize, Debug)]
23 struct Order {
24 uint256 salt;
25 address maker;
26 address signer;
27 address taker;
28 uint256 tokenId;
29 uint256 makerAmount;
30 uint256 takerAmount;
31 uint256 expiration;
32 uint256 nonce;
33 uint256 feeRateBps;
34 uint8 side;
35 uint8 signatureType;
36 }
37}
38
39pub struct EthersSigner {
41 wallet: Arc<PrivateKeySigner>,
42}
43
44impl EthersSigner {
45 pub fn new_from_private_key(hex_priv: &str) -> Result<Self, ClobError> {
47 let w = PrivateKeySigner::from_str(hex_priv)
48 .map_err(|e| ClobError::Other(format!("invalid private key: {}", e)))?;
49 Ok(Self {
50 wallet: Arc::new(w),
51 })
52 }
53
54 pub fn wallet(&self) -> Arc<PrivateKeySigner> {
56 self.wallet.clone()
57 }
58}
59
60#[async_trait]
61impl Eip712Signer for EthersSigner {
62 async fn get_address(&self) -> Result<String, ClobError> {
63 Ok(format!("{:#x}", self.wallet.address()))
65 }
66
67 async fn sign_typed_data(
68 &self,
69 domain: &str,
70 types: &str,
71 value: &str,
72 ) -> Result<String, ClobError> {
73 let domain_val: serde_json::Value = serde_json::from_str(domain)
75 .map_err(|e| ClobError::Other(format!("invalid domain json: {}", e)))?;
76
77 let name = domain_val
78 .get("name")
79 .and_then(|v| v.as_str())
80 .map(|s| s.to_string().into());
81 let version = domain_val
82 .get("version")
83 .and_then(|v| v.as_str())
84 .map(|s| s.to_string().into());
85 let chain_id = domain_val
86 .get("chainId")
87 .and_then(|v| v.as_u64())
88 .map(|v| U256::from(v));
89 let verifying_contract = domain_val
90 .get("verifyingContract")
91 .and_then(|v| v.as_str())
92 .map(|s| Address::from_str(s).unwrap_or_default());
93
94 let domain_struct = Eip712Domain {
95 name,
96 version,
97 chain_id,
98 verifying_contract,
99 salt: None,
100 };
101
102 let types_val: serde_json::Value = serde_json::from_str(types)
104 .map_err(|e| ClobError::Other(format!("invalid types json: {}", e)))?;
105
106 let primary_type = if let Some(map) = types_val.as_object() {
107 if map.contains_key("ClobAuth") {
108 "ClobAuth"
109 } else {
110 "Order"
111 }
112 } else {
113 "Order"
114 };
115
116 let sig = match primary_type {
117 "ClobAuth" => {
118 let auth: ClobAuth = serde_json::from_str(value)
119 .map_err(|e| ClobError::Other(format!("failed to parse ClobAuth: {}", e)))?;
120 let hash = auth.eip712_signing_hash(&domain_struct);
121 self.wallet.sign_hash(&hash).await
122 }
123 "Order" => {
124 let order: Order = serde_json::from_str(value)
125 .map_err(|e| ClobError::Other(format!("failed to parse Order: {}", e)))?;
126 let hash = order.eip712_signing_hash(&domain_struct);
127 self.wallet.sign_hash(&hash).await
128 }
129 _ => {
130 return Err(ClobError::Other(format!(
131 "unknown primary type: {}",
132 primary_type
133 )));
134 }
135 };
136
137 match sig {
138 Ok(s) => {
139 let mut bytes = s.as_bytes();
143 if bytes[64] < 27 {
144 bytes[64] += 27;
145 }
146 Ok(hex::encode(bytes))
147 }
148 Err(e) => Err(ClobError::Other(format!("sign error: {}", e))),
149 }
150 }
151}