1use crate::errors::{PolyfillError, Result};
7use crate::types::ApiCredentials;
8use alloy_primitives::{hex::encode_prefixed, Address, B256, U256};
9use alloy_signer::SignerSync;
10use alloy_signer_local::PrivateKeySigner;
11use alloy_sol_types::{eip712_domain, sol, Eip712Domain, SolStruct};
12use base64::engine::Engine;
13use hmac::{Hmac, Mac};
14use serde::Serialize;
15use sha2::Sha256;
16use std::borrow::Cow;
17use std::collections::HashMap;
18use std::ops::Deref;
19use std::sync::Arc;
20use std::time::{SystemTime, UNIX_EPOCH};
21
22const POLY_ADDR_HEADER: &str = "poly_address";
24const POLY_SIG_HEADER: &str = "poly_signature";
25const POLY_TS_HEADER: &str = "poly_timestamp";
26const POLY_NONCE_HEADER: &str = "poly_nonce";
27const POLY_API_KEY_HEADER: &str = "poly_api_key";
28const POLY_PASS_HEADER: &str = "poly_passphrase";
29
30type Headers = HashMap<&'static str, String>;
31
32pub trait HmacApiCredentials {
33 fn api_key(&self) -> &str;
34 fn passphrase(&self) -> &str;
35 fn decoded_secret_bytes(&self) -> Result<Cow<'_, [u8]>>;
36}
37
38#[derive(Debug, Clone)]
39pub struct PreparedApiCredentials {
40 credentials: ApiCredentials,
41 decoded_secret: Arc<[u8]>,
42}
43
44impl PreparedApiCredentials {
45 pub fn try_new(credentials: ApiCredentials) -> Result<Self> {
46 let decoded_secret = base64::engine::general_purpose::URL_SAFE
47 .decode(&credentials.secret)
48 .map(Into::into)
49 .map_err(|e| PolyfillError::crypto(format!("Failed to decode base64 secret: {e}")))?;
50
51 Ok(Self {
52 credentials,
53 decoded_secret,
54 })
55 }
56
57 pub fn credentials(&self) -> &ApiCredentials {
58 &self.credentials
59 }
60}
61
62impl Deref for PreparedApiCredentials {
63 type Target = ApiCredentials;
64
65 fn deref(&self) -> &Self::Target {
66 &self.credentials
67 }
68}
69
70impl HmacApiCredentials for ApiCredentials {
71 fn api_key(&self) -> &str {
72 &self.api_key
73 }
74
75 fn passphrase(&self) -> &str {
76 &self.passphrase
77 }
78
79 fn decoded_secret_bytes(&self) -> Result<Cow<'_, [u8]>> {
80 Ok(Cow::Owned(decode_secret_bytes(&self.secret)?))
81 }
82}
83
84impl HmacApiCredentials for PreparedApiCredentials {
85 fn api_key(&self) -> &str {
86 &self.credentials.api_key
87 }
88
89 fn passphrase(&self) -> &str {
90 &self.credentials.passphrase
91 }
92
93 fn decoded_secret_bytes(&self) -> Result<Cow<'_, [u8]>> {
94 Ok(Cow::Borrowed(self.decoded_secret.as_ref()))
95 }
96}
97
98sol! {
100 struct ClobAuth {
101 address address;
102 string timestamp;
103 uint256 nonce;
104 string message;
105 }
106}
107
108sol! {
110 struct Order {
111 uint256 salt;
112 address maker;
113 address signer;
114 uint256 tokenId;
115 uint256 makerAmount;
116 uint256 takerAmount;
117 uint8 side;
118 uint8 signatureType;
119 uint256 timestamp;
120 bytes32 metadata;
121 bytes32 builder;
122 }
123
124 struct TypedDataSign {
125 Order contents;
126 string name;
127 string version;
128 uint256 chainId;
129 address verifyingContract;
130 bytes32 salt;
131 }
132}
133
134const DEPOSIT_WALLET_NAME: &str = "DepositWallet";
135const DEPOSIT_WALLET_VERSION: &str = "1";
136const ERC7739_TYPED_DATA_SIGN_SALT: B256 = B256::ZERO;
137const ORDER_TYPE_STRING: &str = "Order(uint256 salt,address maker,address signer,uint256 tokenId,uint256 makerAmount,uint256 takerAmount,uint8 side,uint8 signatureType,uint256 timestamp,bytes32 metadata,bytes32 builder)";
138
139#[derive(Clone)]
142pub struct SignedOrderMessage {
143 pub salt: U256,
144 pub maker: Address,
145 pub signer: Address,
146 pub token_id: U256,
147 pub maker_amount: U256,
148 pub taker_amount: U256,
149 pub side: u8,
150 pub signature_type: u8,
151 pub timestamp: U256,
152 pub metadata: B256,
153 pub builder: B256,
154}
155
156#[derive(Clone)]
158pub struct PreparedOrderDomain {
159 domain: Eip712Domain,
160}
161
162impl PreparedOrderDomain {
163 pub fn new(chain_id: u64, verifying_contract: Address) -> Self {
164 let domain = eip712_domain!(
165 name: "Polymarket CTF Exchange",
166 version: "2",
167 chain_id: chain_id,
168 verifying_contract: verifying_contract,
169 );
170
171 Self { domain }
172 }
173}
174
175pub fn get_current_unix_time_secs() -> u64 {
177 SystemTime::now()
178 .duration_since(UNIX_EPOCH)
179 .expect("Time went backwards")
180 .as_secs()
181}
182
183pub fn sign_clob_auth_message(
185 signer: &PrivateKeySigner,
186 timestamp: String,
187 nonce: U256,
188) -> Result<String> {
189 let message = "This message attests that I control the given wallet".to_string();
190 let polygon = 137;
191
192 let auth_struct = ClobAuth {
193 address: signer.address(),
194 timestamp,
195 nonce,
196 message,
197 };
198
199 let domain = eip712_domain!(
200 name: "ClobAuthDomain",
201 version: "1",
202 chain_id: polygon,
203 );
204
205 let signature = signer
206 .sign_typed_data_sync(&auth_struct, &domain)
207 .map_err(|e| PolyfillError::crypto(format!("EIP-712 signature failed: {}", e)))?;
208
209 Ok(encode_prefixed(signature.as_bytes()))
210}
211
212pub fn sign_order_message(
214 signer: &PrivateKeySigner,
215 order: SignedOrderMessage,
216 chain_id: u64,
217 verifying_contract: Address,
218) -> Result<String> {
219 let domain = PreparedOrderDomain::new(chain_id, verifying_contract);
220 sign_order_message_with_domain(signer, order, &domain)
221}
222
223pub fn sign_order_message_with_domain(
225 signer: &PrivateKeySigner,
226 order: SignedOrderMessage,
227 domain: &PreparedOrderDomain,
228) -> Result<String> {
229 let order = order_sol(order);
230
231 let signature = signer
232 .sign_typed_data_sync(&order, &domain.domain)
233 .map_err(|e| PolyfillError::crypto(format!("Order signature failed: {}", e)))?;
234
235 Ok(encode_prefixed(signature.as_bytes()))
236}
237
238pub fn sign_poly1271_order_message_with_domain(
247 signer: &PrivateKeySigner,
248 order: SignedOrderMessage,
249 domain: &PreparedOrderDomain,
250 deposit_wallet: Address,
251 chain_id: u64,
252) -> Result<String> {
253 if order.maker != deposit_wallet || order.signer != deposit_wallet {
254 return Err(PolyfillError::validation(
255 "POLY_1271 orders require maker and signer to equal the deposit wallet",
256 ));
257 }
258
259 let order = order_sol(order);
260 let app_domain_separator = domain.domain.hash_struct();
261 let contents_hash = order.eip712_hash_struct();
262 let envelope = TypedDataSign {
263 contents: order,
264 name: DEPOSIT_WALLET_NAME.to_string(),
265 version: DEPOSIT_WALLET_VERSION.to_string(),
266 chainId: U256::from(chain_id),
267 verifyingContract: deposit_wallet,
268 salt: ERC7739_TYPED_DATA_SIGN_SALT,
269 };
270
271 let inner = signer
272 .sign_typed_data_sync(&envelope, &domain.domain)
273 .map_err(|e| PolyfillError::crypto(format!("POLY_1271 order signature failed: {e}")))?;
274
275 let type_bytes = ORDER_TYPE_STRING.as_bytes();
276 let type_len: u16 = type_bytes
277 .len()
278 .try_into()
279 .map_err(|_| PolyfillError::validation("POLY_1271 order type string too long"))?;
280 let mut wrapped = Vec::with_capacity(65 + 32 + 32 + type_bytes.len() + 2);
281 wrapped.extend_from_slice(&inner.as_bytes());
282 wrapped.extend_from_slice(app_domain_separator.as_slice());
283 wrapped.extend_from_slice(contents_hash.as_slice());
284 wrapped.extend_from_slice(type_bytes);
285 wrapped.extend_from_slice(&type_len.to_be_bytes());
286
287 Ok(encode_prefixed(wrapped))
288}
289
290fn order_sol(order: SignedOrderMessage) -> Order {
291 Order {
292 salt: order.salt,
293 maker: order.maker,
294 signer: order.signer,
295 tokenId: order.token_id,
296 makerAmount: order.maker_amount,
297 takerAmount: order.taker_amount,
298 side: order.side,
299 signatureType: order.signature_type,
300 timestamp: order.timestamp,
301 metadata: order.metadata,
302 builder: order.builder,
303 }
304}
305
306pub fn build_hmac_signature<T>(
311 secret: &str,
312 timestamp: u64,
313 method: &str,
314 request_path: &str,
315 body: Option<&T>,
316) -> Result<String>
317where
318 T: ?Sized + Serialize,
319{
320 let decoded_secret = decode_secret_bytes(secret)?;
321 let body_bytes =
322 match body {
323 Some(b) => Some(serde_json::to_vec(b).map_err(|e| {
324 PolyfillError::parse(format!("Failed to serialize body: {}", e), None)
325 })?),
326 None => None,
327 };
328
329 build_hmac_signature_bytes(
330 &decoded_secret,
331 timestamp,
332 method,
333 request_path,
334 body_bytes.as_deref(),
335 )
336}
337
338pub fn build_hmac_signature_bytes(
339 decoded_secret: &[u8],
340 timestamp: u64,
341 method: &str,
342 request_path: &str,
343 body_bytes: Option<&[u8]>,
344) -> Result<String> {
345 let mut mac = Hmac::<Sha256>::new_from_slice(decoded_secret)
346 .map_err(|e| PolyfillError::crypto(format!("Invalid HMAC key: {}", e)))?;
347
348 let timestamp = timestamp.to_string();
349 mac.update(timestamp.as_bytes());
350 let method_upper;
351 let method_bytes = if method.bytes().all(|b| !b.is_ascii_lowercase()) {
352 method.as_bytes()
353 } else {
354 method_upper = method.to_ascii_uppercase();
355 method_upper.as_bytes()
356 };
357 mac.update(method_bytes);
358 mac.update(request_path.as_bytes());
359 if let Some(body_bytes) = body_bytes {
360 mac.update(body_bytes);
361 }
362
363 let result = mac.finalize();
364
365 Ok(base64::engine::general_purpose::URL_SAFE.encode(result.into_bytes()))
366}
367
368fn decode_secret_bytes(secret: &str) -> Result<Vec<u8>> {
369 base64::engine::general_purpose::URL_SAFE
370 .decode(secret)
371 .map_err(|e| PolyfillError::crypto(format!("Failed to decode base64 secret: {}", e)))
372}
373
374pub fn create_l1_headers(signer: &PrivateKeySigner, nonce: Option<U256>) -> Result<Headers> {
379 let timestamp = get_current_unix_time_secs().to_string();
381 let nonce = nonce.unwrap_or(U256::ZERO);
382
383 let signature = sign_clob_auth_message(signer, timestamp.clone(), nonce)?;
385 let address = encode_prefixed(signer.address().as_slice());
386
387 Ok(HashMap::from([
389 (POLY_ADDR_HEADER, address),
390 (POLY_SIG_HEADER, signature),
391 (POLY_TS_HEADER, timestamp),
392 (POLY_NONCE_HEADER, nonce.to_string()),
393 ]))
394}
395
396pub fn create_l2_headers<T>(
401 signer: &PrivateKeySigner,
402 api_creds: &(impl HmacApiCredentials + ?Sized),
403 method: &str,
404 req_path: &str,
405 body: Option<&T>,
406) -> Result<Headers>
407where
408 T: ?Sized + Serialize,
409{
410 let address = encode_prefixed(signer.address().as_slice());
412 let timestamp = get_current_unix_time_secs();
413
414 let decoded_secret = api_creds.decoded_secret_bytes()?;
416 let body_bytes =
417 match body {
418 Some(b) => Some(serde_json::to_vec(b).map_err(|e| {
419 PolyfillError::parse(format!("Failed to serialize body: {}", e), None)
420 })?),
421 None => None,
422 };
423 let hmac_signature = build_hmac_signature_bytes(
424 &decoded_secret,
425 timestamp,
426 method,
427 req_path,
428 body_bytes.as_deref(),
429 )?;
430
431 Ok(HashMap::from([
433 (POLY_ADDR_HEADER, address),
434 (POLY_SIG_HEADER, hmac_signature),
435 (POLY_TS_HEADER, timestamp.to_string()),
436 (POLY_API_KEY_HEADER, api_creds.api_key().to_string()),
437 (POLY_PASS_HEADER, api_creds.passphrase().to_string()),
438 ]))
439}
440
441pub fn create_l2_headers_with_body_bytes(
442 signer: &PrivateKeySigner,
443 api_creds: &(impl HmacApiCredentials + ?Sized),
444 method: &str,
445 req_path: &str,
446 body_bytes: Option<&[u8]>,
447) -> Result<Headers> {
448 let address = encode_prefixed(signer.address().as_slice());
449 let timestamp = get_current_unix_time_secs();
450 let decoded_secret = api_creds.decoded_secret_bytes()?;
451 let hmac_signature =
452 build_hmac_signature_bytes(&decoded_secret, timestamp, method, req_path, body_bytes)?;
453
454 Ok(HashMap::from([
455 (POLY_ADDR_HEADER, address),
456 (POLY_SIG_HEADER, hmac_signature),
457 (POLY_TS_HEADER, timestamp.to_string()),
458 (POLY_API_KEY_HEADER, api_creds.api_key().to_string()),
459 (POLY_PASS_HEADER, api_creds.passphrase().to_string()),
460 ]))
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466 use std::str::FromStr;
467
468 #[test]
469 fn test_unix_timestamp() {
470 let timestamp = get_current_unix_time_secs();
471 assert!(timestamp > 1_600_000_000); }
473
474 #[test]
475 fn test_hmac_signature() {
476 let result = build_hmac_signature::<String>(
477 "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1",
478 1234567890,
479 "GET",
480 "/test",
481 None,
482 );
483 assert!(result.is_ok());
484 }
485
486 #[test]
487 fn test_hmac_signature_with_body() {
488 let body = r#"{"test": "data"}"#;
489 let result = build_hmac_signature(
490 "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1",
491 1234567890,
492 "POST",
493 "/orders",
494 Some(body),
495 );
496 assert!(result.is_ok());
497 let signature = result.unwrap();
498 assert!(!signature.is_empty());
499 }
500
501 #[test]
502 fn test_hmac_signature_consistency() {
503 let secret = "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1";
504 let timestamp = 1234567890;
505 let method = "GET";
506 let path = "/test";
507
508 let sig1 = build_hmac_signature::<String>(secret, timestamp, method, path, None).unwrap();
509 let sig2 = build_hmac_signature::<String>(secret, timestamp, method, path, None).unwrap();
510
511 assert_eq!(sig1, sig2);
513 }
514
515 #[test]
516 fn test_hmac_signature_bytes_matches_serialized_body() {
517 let secret = "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1";
518 let timestamp = 1234567890;
519 let body = serde_json::json!({"orderID": "abc123"});
520 let body_bytes = serde_json::to_vec(&body).unwrap();
521 let decoded_secret = decode_secret_bytes(secret).unwrap();
522
523 let object_signature =
524 build_hmac_signature(secret, timestamp, "delete", "/order", Some(&body)).unwrap();
525 let bytes_signature = build_hmac_signature_bytes(
526 &decoded_secret,
527 timestamp,
528 "DELETE",
529 "/order",
530 Some(&body_bytes),
531 )
532 .unwrap();
533
534 assert_eq!(object_signature, bytes_signature);
535 }
536
537 #[test]
538 fn test_hmac_signature_different_inputs() {
539 let secret = "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1";
540 let timestamp = 1234567890;
541
542 let sig1 = build_hmac_signature::<String>(secret, timestamp, "GET", "/test", None).unwrap();
543 let sig2 =
544 build_hmac_signature::<String>(secret, timestamp, "POST", "/test", None).unwrap();
545 let sig3 =
546 build_hmac_signature::<String>(secret, timestamp, "GET", "/other", None).unwrap();
547
548 assert_ne!(sig1, sig2);
550 assert_ne!(sig1, sig3);
551 assert_ne!(sig2, sig3);
552 }
553
554 #[test]
555 fn test_create_l1_headers() {
556 use alloy_primitives::U256;
557 use alloy_signer_local::PrivateKeySigner;
558
559 let private_key = "0x1234567890123456789012345678901234567890123456789012345678901234";
560 let signer: PrivateKeySigner = private_key.parse().expect("Valid private key");
561
562 let result = create_l1_headers(&signer, Some(U256::from(12345)));
563 assert!(result.is_ok());
564
565 let headers = result.unwrap();
566 assert!(headers.contains_key("poly_address"));
567 assert!(headers.contains_key("poly_signature"));
568 assert!(headers.contains_key("poly_timestamp"));
569 assert!(headers.contains_key("poly_nonce"));
570 }
571
572 #[test]
573 fn test_create_l1_headers_different_nonces() {
574 use alloy_primitives::U256;
575 use alloy_signer_local::PrivateKeySigner;
576
577 let private_key = "0x1234567890123456789012345678901234567890123456789012345678901234";
578 let signer: PrivateKeySigner = private_key.parse().expect("Valid private key");
579
580 let headers_1 = create_l1_headers(&signer, Some(U256::from(12345))).unwrap();
581 let headers_2 = create_l1_headers(&signer, Some(U256::from(54321))).unwrap();
582
583 assert_ne!(
585 headers_1.get("poly_signature"),
586 headers_2.get("poly_signature")
587 );
588
589 assert_eq!(headers_1.get("poly_address"), headers_2.get("poly_address"));
591 }
592
593 #[test]
594 fn test_create_l2_headers() {
595 use alloy_signer_local::PrivateKeySigner;
596
597 let private_key = "0x1234567890123456789012345678901234567890123456789012345678901234";
598 let signer: PrivateKeySigner = private_key.parse().expect("Valid private key");
599
600 let api_creds = ApiCredentials {
601 api_key: "test_key".to_string(),
602 secret: "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1".to_string(),
603 passphrase: "test_passphrase".to_string(),
604 };
605
606 let result = create_l2_headers::<String>(&signer, &api_creds, "GET", "/test", None);
607 assert!(result.is_ok());
608
609 let headers = result.unwrap();
610 assert!(headers.contains_key("poly_api_key"));
611 assert!(headers.contains_key("poly_signature"));
612 assert!(headers.contains_key("poly_timestamp"));
613 assert!(headers.contains_key("poly_passphrase"));
614
615 assert_eq!(headers.get("poly_api_key").unwrap(), "test_key");
616 assert_eq!(headers.get("poly_passphrase").unwrap(), "test_passphrase");
617 }
618
619 #[test]
620 fn test_eip712_signature_format() {
621 use alloy_primitives::U256;
622 use alloy_signer_local::PrivateKeySigner;
623
624 let private_key = "0x1234567890123456789012345678901234567890123456789012345678901234";
625 let signer: PrivateKeySigner = private_key.parse().expect("Valid private key");
626
627 let result = create_l1_headers(&signer, Some(U256::from(12345)));
629 assert!(result.is_ok());
630
631 let headers = result.unwrap();
632 let signature = headers.get("poly_signature").unwrap();
633
634 assert!(signature.starts_with("0x"));
636 assert_eq!(signature.len(), 132); }
638
639 #[test]
640 fn test_poly1271_wrapped_order_signature_golden() {
641 let signer: PrivateKeySigner =
642 "0x2222222222222222222222222222222222222222222222222222222222222222"
643 .parse()
644 .expect("valid private key");
645 let deposit_wallet =
646 Address::from_str("0x000000000000000000000000000000000000d077").unwrap();
647 let exchange = Address::from_str("0xE111180000d2663C0091e4f400237545B87B996B").unwrap();
648 let order = SignedOrderMessage {
649 salt: U256::from(12345),
650 maker: deposit_wallet,
651 signer: deposit_wallet,
652 token_id: U256::from_str_radix(
653 "100000000000000000000000000000000000000000000000000000000000000000000000000000",
654 10,
655 )
656 .unwrap(),
657 maker_amount: U256::from(1_000_000),
658 taker_amount: U256::from(5_000_000),
659 side: 0,
660 signature_type: 3,
661 timestamp: U256::from(1_716_000_000_000_u64),
662 metadata: B256::ZERO,
663 builder: B256::ZERO,
664 };
665
666 let got = sign_poly1271_order_message_with_domain(
667 &signer,
668 order,
669 &PreparedOrderDomain::new(137, exchange),
670 deposit_wallet,
671 137,
672 )
673 .unwrap();
674
675 const WANT: &str = "0xc1c199d3822d7f465b1f213793ebb7bbc3a77810ceefa89b58ecfeb284e708953c5ec946a41c6fa2a6fac373c78b167dd6834fae6f3e37581111ffb06200f1d21b3264e159346253e26a64e00b69032db0e7d32f94628de3e6eecb50304d7af3d26814859c22020105275eba8b46528be2edd6078dea415bec6d90f2076b0c8ac64f726465722875696e743235362073616c742c61646472657373206d616b65722c61646472657373207369676e65722c75696e7432353620746f6b656e49642c75696e74323536206d616b6572416d6f756e742c75696e743235362074616b6572416d6f756e742c75696e743820736964652c75696e7438207369676e6174757265547970652c75696e743235362074696d657374616d702c62797465733332206d657461646174612c62797465733332206275696c6465722900ba";
676 assert_eq!(got, WANT);
677 assert!(got.len() > 600 && got.len() < 700);
678 }
679
680 #[test]
681 fn test_timestamp_generation() {
682 let ts1 = get_current_unix_time_secs();
683 std::thread::sleep(std::time::Duration::from_millis(1));
684 let ts2 = get_current_unix_time_secs();
685
686 assert!(ts2 >= ts1);
688
689 assert!(ts1 > 1_600_000_000);
691 assert!(ts1 < 1_900_000_000);
692 }
693}