1use crate::errors::{PolyfillError, Result};
7use crate::types::ApiCredentials;
8use alloy_primitives::{hex::encode_prefixed, Address, U256};
9use alloy_signer::SignerSync;
10use alloy_signer_local::PrivateKeySigner;
11use alloy_sol_types::{eip712_domain, sol};
12use base64::engine::Engine;
13use hmac::{Hmac, Mac};
14use serde::Serialize;
15use sha2::Sha256;
16use std::collections::HashMap;
17use std::time::{SystemTime, UNIX_EPOCH};
18
19const POLY_ADDR_HEADER: &str = "poly_address";
21const POLY_SIG_HEADER: &str = "poly_signature";
22const POLY_TS_HEADER: &str = "poly_timestamp";
23const POLY_NONCE_HEADER: &str = "poly_nonce";
24const POLY_API_KEY_HEADER: &str = "poly_api_key";
25const POLY_PASS_HEADER: &str = "poly_passphrase";
26
27type Headers = HashMap<&'static str, String>;
28
29sol! {
31 struct ClobAuth {
32 address address;
33 string timestamp;
34 uint256 nonce;
35 string message;
36 }
37}
38
39sol! {
41 struct Order {
42 uint256 salt;
43 address maker;
44 address signer;
45 address taker;
46 uint256 tokenId;
47 uint256 makerAmount;
48 uint256 takerAmount;
49 uint256 expiration;
50 uint256 nonce;
51 uint256 feeRateBps;
52 uint8 side;
53 uint8 signatureType;
54 }
55}
56
57pub fn get_current_unix_time_secs() -> u64 {
59 SystemTime::now()
60 .duration_since(UNIX_EPOCH)
61 .expect("Time went backwards")
62 .as_secs()
63}
64
65pub fn sign_clob_auth_message(
67 signer: &PrivateKeySigner,
68 timestamp: String,
69 nonce: U256,
70) -> Result<String> {
71 let message = "This message attests that I control the given wallet".to_string();
72 let polygon = 137;
73
74 let auth_struct = ClobAuth {
75 address: signer.address(),
76 timestamp,
77 nonce,
78 message,
79 };
80
81 let domain = eip712_domain!(
82 name: "ClobAuthDomain",
83 version: "1",
84 chain_id: polygon,
85 );
86
87 let signature = signer
88 .sign_typed_data_sync(&auth_struct, &domain)
89 .map_err(|e| PolyfillError::crypto(format!("EIP-712 signature failed: {}", e)))?;
90
91 Ok(encode_prefixed(signature.as_bytes()))
92}
93
94pub fn sign_order_message(
96 signer: &PrivateKeySigner,
97 order: Order,
98 chain_id: u64,
99 verifying_contract: Address,
100) -> Result<String> {
101 let domain = eip712_domain!(
102 name: "Polymarket CTF Exchange",
103 version: "1",
104 chain_id: chain_id,
105 verifying_contract: verifying_contract,
106 );
107
108 let signature = signer
109 .sign_typed_data_sync(&order, &domain)
110 .map_err(|e| PolyfillError::crypto(format!("Order signature failed: {}", e)))?;
111
112 Ok(encode_prefixed(signature.as_bytes()))
113}
114
115pub fn build_hmac_signature<T>(
117 secret: &str,
118 timestamp: u64,
119 method: &str,
120 request_path: &str,
121 body: Option<&T>,
122) -> Result<String>
123where
124 T: ?Sized + Serialize,
125{
126 let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes())
127 .map_err(|e| PolyfillError::crypto(format!("Invalid HMAC key: {}", e)))?;
128
129 let message = format!(
131 "{}{}{}{}",
132 timestamp,
133 method.to_uppercase(),
134 request_path,
135 match body {
136 Some(b) => serde_json::to_string(b).map_err(|e| PolyfillError::parse(
137 format!("Failed to serialize body: {}", e),
138 None
139 ))?,
140 None => String::new(),
141 }
142 );
143
144 mac.update(message.as_bytes());
145 let result = mac.finalize();
146 Ok(base64::engine::general_purpose::STANDARD.encode(result.into_bytes()))
147}
148
149pub fn create_l1_headers(signer: &PrivateKeySigner, nonce: Option<U256>) -> Result<Headers> {
151 let timestamp = get_current_unix_time_secs().to_string();
152 let nonce = nonce.unwrap_or(U256::ZERO);
153 let signature = sign_clob_auth_message(signer, timestamp.clone(), nonce)?;
154 let address = encode_prefixed(signer.address().as_slice());
155
156 Ok(HashMap::from([
157 (POLY_ADDR_HEADER, address),
158 (POLY_SIG_HEADER, signature),
159 (POLY_TS_HEADER, timestamp),
160 (POLY_NONCE_HEADER, nonce.to_string()),
161 ]))
162}
163
164pub fn create_l2_headers<T>(
166 signer: &PrivateKeySigner,
167 api_creds: &ApiCredentials,
168 method: &str,
169 req_path: &str,
170 body: Option<&T>,
171) -> Result<Headers>
172where
173 T: ?Sized + Serialize,
174{
175 let address = encode_prefixed(signer.address().as_slice());
176 let timestamp = get_current_unix_time_secs();
177
178 let hmac_signature =
179 build_hmac_signature(&api_creds.secret, timestamp, method, req_path, body)?;
180
181 Ok(HashMap::from([
182 (POLY_ADDR_HEADER, address),
183 (POLY_SIG_HEADER, hmac_signature),
184 (POLY_TS_HEADER, timestamp.to_string()),
185 (POLY_API_KEY_HEADER, api_creds.api_key.clone()),
186 (POLY_PASS_HEADER, api_creds.passphrase.clone()),
187 ]))
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn test_unix_timestamp() {
196 let timestamp = get_current_unix_time_secs();
197 assert!(timestamp > 1_600_000_000); }
199
200 #[test]
201 fn test_hmac_signature() {
202 let result =
203 build_hmac_signature::<String>("test_secret", 1234567890, "GET", "/test", None);
204 assert!(result.is_ok());
205 }
206
207 #[test]
208 fn test_hmac_signature_with_body() {
209 let body = r#"{"test": "data"}"#;
210 let result = build_hmac_signature("test_secret", 1234567890, "POST", "/orders", Some(body));
211 assert!(result.is_ok());
212 let signature = result.unwrap();
213 assert!(!signature.is_empty());
214 }
215
216 #[test]
217 fn test_hmac_signature_consistency() {
218 let secret = "test_secret";
219 let timestamp = 1234567890;
220 let method = "GET";
221 let path = "/test";
222
223 let sig1 = build_hmac_signature::<String>(secret, timestamp, method, path, None).unwrap();
224 let sig2 = build_hmac_signature::<String>(secret, timestamp, method, path, None).unwrap();
225
226 assert_eq!(sig1, sig2);
228 }
229
230 #[test]
231 fn test_hmac_signature_different_inputs() {
232 let secret = "test_secret";
233 let timestamp = 1234567890;
234
235 let sig1 = build_hmac_signature::<String>(secret, timestamp, "GET", "/test", None).unwrap();
236 let sig2 =
237 build_hmac_signature::<String>(secret, timestamp, "POST", "/test", None).unwrap();
238 let sig3 =
239 build_hmac_signature::<String>(secret, timestamp, "GET", "/other", None).unwrap();
240
241 assert_ne!(sig1, sig2);
243 assert_ne!(sig1, sig3);
244 assert_ne!(sig2, sig3);
245 }
246
247 #[test]
248 fn test_create_l1_headers() {
249 use alloy_primitives::U256;
250 use alloy_signer_local::PrivateKeySigner;
251
252 let private_key = "0x1234567890123456789012345678901234567890123456789012345678901234";
253 let signer: PrivateKeySigner = private_key.parse().expect("Valid private key");
254
255 let result = create_l1_headers(&signer, Some(U256::from(12345)));
256 assert!(result.is_ok());
257
258 let headers = result.unwrap();
259 assert!(headers.contains_key("poly_address"));
260 assert!(headers.contains_key("poly_signature"));
261 assert!(headers.contains_key("poly_timestamp"));
262 assert!(headers.contains_key("poly_nonce"));
263 }
264
265 #[test]
266 fn test_create_l1_headers_different_nonces() {
267 use alloy_primitives::U256;
268 use alloy_signer_local::PrivateKeySigner;
269
270 let private_key = "0x1234567890123456789012345678901234567890123456789012345678901234";
271 let signer: PrivateKeySigner = private_key.parse().expect("Valid private key");
272
273 let headers_1 = create_l1_headers(&signer, Some(U256::from(12345))).unwrap();
274 let headers_2 = create_l1_headers(&signer, Some(U256::from(54321))).unwrap();
275
276 assert_ne!(
278 headers_1.get("poly_signature"),
279 headers_2.get("poly_signature")
280 );
281
282 assert_eq!(headers_1.get("poly_address"), headers_2.get("poly_address"));
284 }
285
286 #[test]
287 fn test_create_l2_headers() {
288 use alloy_signer_local::PrivateKeySigner;
289
290 let private_key = "0x1234567890123456789012345678901234567890123456789012345678901234";
291 let signer: PrivateKeySigner = private_key.parse().expect("Valid private key");
292
293 let api_creds = ApiCredentials {
294 api_key: "test_key".to_string(),
295 secret: "test_secret".to_string(),
296 passphrase: "test_passphrase".to_string(),
297 };
298
299 let result = create_l2_headers::<String>(&signer, &api_creds, "/test", "GET", None);
300 assert!(result.is_ok());
301
302 let headers = result.unwrap();
303 assert!(headers.contains_key("poly_api_key"));
304 assert!(headers.contains_key("poly_signature"));
305 assert!(headers.contains_key("poly_timestamp"));
306 assert!(headers.contains_key("poly_passphrase"));
307
308 assert_eq!(headers.get("poly_api_key").unwrap(), "test_key");
309 assert_eq!(headers.get("poly_passphrase").unwrap(), "test_passphrase");
310 }
311
312 #[test]
313 fn test_eip712_signature_format() {
314 use alloy_primitives::U256;
315 use alloy_signer_local::PrivateKeySigner;
316
317 let private_key = "0x1234567890123456789012345678901234567890123456789012345678901234";
318 let signer: PrivateKeySigner = private_key.parse().expect("Valid private key");
319
320 let result = create_l1_headers(&signer, Some(U256::from(12345)));
322 assert!(result.is_ok());
323
324 let headers = result.unwrap();
325 let signature = headers.get("poly_signature").unwrap();
326
327 assert!(signature.starts_with("0x"));
329 assert_eq!(signature.len(), 132); }
331
332 #[test]
333 fn test_timestamp_generation() {
334 let ts1 = get_current_unix_time_secs();
335 std::thread::sleep(std::time::Duration::from_millis(1));
336 let ts2 = get_current_unix_time_secs();
337
338 assert!(ts2 >= ts1);
340
341 assert!(ts1 > 1_600_000_000);
343 assert!(ts1 < 1_900_000_000);
344 }
345}