1use std::str::FromStr;
27
28use alloy::{
29 signers::{SignerSync, local::PrivateKeySigner},
30 sol_types::{SolStruct, SolValue, eip712_domain},
31};
32use alloy_primitives::{Address, B256, FixedBytes, U256, address, keccak256};
33use rust_decimal::Decimal;
34
35use crate::{
36 common::{
37 credential::EvmPrivateKey,
38 enums::{PolymarketOrderSide, SignatureType},
39 },
40 http::{
41 error::{Error, Result},
42 models::PolymarketOrder,
43 },
44};
45
46const CLOB_AUTH_DOMAIN_NAME: &str = "ClobAuthDomain";
48const CLOB_AUTH_DOMAIN_VERSION: &str = "1";
49const CLOB_AUTH_MESSAGE: &str = "This message attests that I control the given wallet";
50
51pub const CTF_EXCHANGE: Address = address!("0xE111180000d2663C0091e4f400237545B87B996B");
53
54pub const NEG_RISK_CTF_EXCHANGE: Address = address!("0xe2222d279d744050d28e00520010520000310F59");
56
57const DOMAIN_NAME: &str = "Polymarket CTF Exchange";
58const DOMAIN_VERSION: &str = "2";
59const POLYGON_CHAIN_ID: u64 = 137;
60const ORDER_TYPE_STRING: &str = concat!(
61 "Order(uint256 salt,address maker,address signer,uint256 tokenId,",
62 "uint256 makerAmount,uint256 takerAmount,uint8 side,uint8 signatureType,",
63 "uint256 timestamp,bytes32 metadata,bytes32 builder)",
64);
65const SOLADY_TYPE_STRING: &str = concat!(
66 "TypedDataSign(Order contents,string name,string version,uint256 chainId,",
67 "address verifyingContract,bytes32 salt)",
68 "Order(uint256 salt,address maker,address signer,uint256 tokenId,",
69 "uint256 makerAmount,uint256 takerAmount,uint8 side,uint8 signatureType,",
70 "uint256 timestamp,bytes32 metadata,bytes32 builder)",
71);
72const DOMAIN_TYPE_STRING: &str =
73 "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)";
74const DEPOSIT_WALLET_DOMAIN_NAME: &str = "DepositWallet";
75const DEPOSIT_WALLET_DOMAIN_VERSION: &str = "1";
76
77alloy::sol! {
81 struct ClobAuth {
82 address address;
83 string timestamp;
84 uint256 nonce;
85 string message;
86 }
87}
88
89alloy::sol! {
94 struct Order {
95 uint256 salt;
96 address maker;
97 address signer;
98 uint256 tokenId;
99 uint256 makerAmount;
100 uint256 takerAmount;
101 uint8 side;
102 uint8 signatureType;
103 uint256 timestamp;
104 bytes32 metadata;
105 bytes32 builder;
106 }
107}
108
109#[derive(Debug)]
111pub struct OrderSigner {
112 signer: PrivateKeySigner,
113}
114
115impl OrderSigner {
116 pub fn new(private_key: &EvmPrivateKey) -> Result<Self> {
118 let key_hex = private_key
119 .as_hex()
120 .strip_prefix("0x")
121 .unwrap_or(private_key.as_hex());
122 let signer = PrivateKeySigner::from_str(key_hex)
123 .map_err(|e| Error::bad_request(format!("Failed to create signer: {e}")))?;
124 Ok(Self { signer })
125 }
126
127 #[must_use]
129 pub fn address(&self) -> Address {
130 self.signer.address()
131 }
132
133 pub fn sign_order(&self, order: &PolymarketOrder, neg_risk: bool) -> Result<String> {
144 let order_signer = parse_address(&order.signer, "signer")?;
145 let order_maker = parse_address(&order.maker, "maker")?;
146 if order.signature_type == SignatureType::Poly1271 {
147 if order_signer != order_maker {
148 return Err(Error::bad_request(format!(
149 "POLY_1271 orders require maker and signer to both be the deposit wallet, maker was {order_maker}, signer was {order_signer}",
150 )));
151 }
152 } else if order_signer != self.signer.address() {
153 return Err(Error::bad_request(format!(
154 "Order signer {order_signer} does not match local signer {}",
155 self.signer.address(),
156 )));
157 }
158
159 let eip712_order = build_eip712_order(order)?;
160 let contract = exchange_contract(neg_risk);
161
162 if order.signature_type == SignatureType::Poly1271 {
163 return self.sign_poly_1271_order(&eip712_order, contract);
164 }
165
166 let domain = eip712_domain! {
167 name: DOMAIN_NAME,
168 version: DOMAIN_VERSION,
169 chain_id: POLYGON_CHAIN_ID,
170 verifying_contract: contract,
171 };
172
173 let signing_hash = eip712_order.eip712_signing_hash(&domain);
174 self.sign_hash(&signing_hash.0)
175 }
176
177 fn sign_poly_1271_order(&self, order: &Order, contract: Address) -> Result<String> {
178 let contents_hash = poly_1271_contents_hash(order);
179 let wallet_struct_hash = poly_1271_wallet_struct_hash(contents_hash, order.signer);
180 let app_domain_separator = ctf_exchange_domain_separator(contract);
181 let signing_hash = typed_data_hash(app_domain_separator, wallet_struct_hash);
182 let signature = self.sign_hash_b256(&signing_hash)?;
183
184 let mut encoded =
185 Vec::with_capacity(65 + 32 + 32 + ORDER_TYPE_STRING.len() + std::mem::size_of::<u16>());
186 encoded.extend_from_slice(&signature);
187 encoded.extend_from_slice(app_domain_separator.as_slice());
188 encoded.extend_from_slice(contents_hash.as_slice());
189 encoded.extend_from_slice(ORDER_TYPE_STRING.as_bytes());
190 encoded.extend_from_slice(&(ORDER_TYPE_STRING.len() as u16).to_be_bytes());
191
192 Ok(format!(
193 "0x{}",
194 alloy_primitives::hex::encode(encoded.as_slice())
195 ))
196 }
197
198 fn sign_hash_b256(&self, hash: &B256) -> Result<[u8; 65]> {
199 let signature = self
200 .signer
201 .sign_hash_sync(hash)
202 .map_err(|e| Error::bad_request(format!("Failed to sign order: {e}")))?;
203 Ok(signature.as_bytes())
204 }
205
206 fn sign_hash(&self, hash: &[u8; 32]) -> Result<String> {
207 let hash_b256 = B256::from(*hash);
208 let signature = self.sign_hash_b256(&hash_b256)?;
209 Ok(format!(
210 "0x{}",
211 alloy_primitives::hex::encode(signature.as_slice())
212 ))
213 }
214}
215
216pub fn order_hash(order: &PolymarketOrder, neg_risk: bool) -> Result<B256> {
221 let eip712_order = build_eip712_order(order)?;
222 let contract = exchange_contract(neg_risk);
223
224 let domain = eip712_domain! {
225 name: DOMAIN_NAME,
226 version: DOMAIN_VERSION,
227 chain_id: POLYGON_CHAIN_ID,
228 verifying_contract: contract,
229 };
230
231 Ok(eip712_order.eip712_signing_hash(&domain))
232}
233
234const fn exchange_contract(neg_risk: bool) -> Address {
235 if neg_risk {
236 NEG_RISK_CTF_EXCHANGE
237 } else {
238 CTF_EXCHANGE
239 }
240}
241
242fn order_type_hash() -> B256 {
243 keccak256(ORDER_TYPE_STRING.as_bytes())
244}
245
246fn solady_type_hash() -> B256 {
247 keccak256(SOLADY_TYPE_STRING.as_bytes())
248}
249
250fn domain_type_hash() -> B256 {
251 keccak256(DOMAIN_TYPE_STRING.as_bytes())
252}
253
254fn domain_name_hash() -> B256 {
255 keccak256(DOMAIN_NAME.as_bytes())
256}
257
258fn domain_version_hash() -> B256 {
259 keccak256(DOMAIN_VERSION.as_bytes())
260}
261
262fn deposit_wallet_name_hash() -> B256 {
263 keccak256(DEPOSIT_WALLET_DOMAIN_NAME.as_bytes())
264}
265
266fn deposit_wallet_version_hash() -> B256 {
267 keccak256(DEPOSIT_WALLET_DOMAIN_VERSION.as_bytes())
268}
269
270fn poly_1271_contents_hash(order: &Order) -> B256 {
271 let tuple = (
272 order_type_hash(),
273 order.salt,
274 order.maker,
275 order.signer,
276 order.tokenId,
277 order.makerAmount,
278 order.takerAmount,
279 U256::from(order.side),
280 U256::from(order.signatureType),
281 order.timestamp,
282 order.metadata,
283 order.builder,
284 );
285 keccak256(tuple.abi_encode())
286}
287
288fn poly_1271_wallet_struct_hash(contents_hash: B256, deposit_wallet: Address) -> B256 {
289 let tuple = (
290 solady_type_hash(),
291 contents_hash,
292 deposit_wallet_name_hash(),
293 deposit_wallet_version_hash(),
294 U256::from(POLYGON_CHAIN_ID),
295 deposit_wallet,
296 FixedBytes::<32>::ZERO,
297 );
298 keccak256(tuple.abi_encode())
299}
300
301fn ctf_exchange_domain_separator(contract: Address) -> B256 {
302 let tuple = (
303 domain_type_hash(),
304 domain_name_hash(),
305 domain_version_hash(),
306 U256::from(POLYGON_CHAIN_ID),
307 contract,
308 );
309 keccak256(tuple.abi_encode())
310}
311
312fn typed_data_hash(domain_separator: B256, struct_hash: B256) -> B256 {
313 let mut bytes = Vec::with_capacity(2 + 32 + 32);
314 bytes.push(0x19);
315 bytes.push(0x01);
316 bytes.extend_from_slice(domain_separator.as_slice());
317 bytes.extend_from_slice(struct_hash.as_slice());
318 keccak256(bytes)
319}
320
321pub fn sign_clob_auth(
328 private_key: &EvmPrivateKey,
329 timestamp: &str,
330 nonce: u64,
331) -> Result<(String, String)> {
332 let key_hex = private_key
333 .as_hex()
334 .strip_prefix("0x")
335 .unwrap_or(private_key.as_hex());
336 let signer = PrivateKeySigner::from_str(key_hex)
337 .map_err(|e| Error::bad_request(format!("Failed to create signer: {e}")))?;
338
339 let address = signer.address();
340
341 let auth = ClobAuth {
342 address,
343 timestamp: timestamp.to_string(),
344 nonce: U256::from(nonce),
345 message: CLOB_AUTH_MESSAGE.to_string(),
346 };
347
348 let domain = eip712_domain! {
349 name: CLOB_AUTH_DOMAIN_NAME,
350 version: CLOB_AUTH_DOMAIN_VERSION,
351 chain_id: POLYGON_CHAIN_ID,
352 };
353
354 let signing_hash = auth.eip712_signing_hash(&domain);
355 let signature = signer
356 .sign_hash_sync(&signing_hash)
357 .map_err(|e| Error::bad_request(format!("Failed to sign ClobAuth: {e}")))?;
358
359 let r = signature.r();
360 let s = signature.s();
361 let v = if signature.v() { 28u8 } else { 27u8 };
362
363 Ok((
364 format!("{address:#x}"),
365 format!("0x{r:064x}{s:064x}{v:02x}"),
366 ))
367}
368
369fn build_eip712_order(order: &PolymarketOrder) -> Result<Order> {
371 Ok(Order {
372 salt: U256::from(order.salt),
373 maker: parse_address(&order.maker, "maker")?,
374 signer: parse_address(&order.signer, "signer")?,
375 tokenId: U256::from_str(order.token_id.as_str())
376 .map_err(|e| Error::bad_request(format!("Invalid token ID: {e}")))?,
377 makerAmount: decimal_to_u256(order.maker_amount, "maker_amount")?,
378 takerAmount: decimal_to_u256(order.taker_amount, "taker_amount")?,
379 side: order_side_to_u8(order.side),
380 signatureType: order.signature_type as u8,
381 timestamp: U256::from_str(&order.timestamp)
382 .map_err(|e| Error::bad_request(format!("Invalid timestamp: {e}")))?,
383 metadata: parse_bytes32(&order.metadata, "metadata")?,
384 builder: parse_bytes32(&order.builder, "builder")?,
385 })
386}
387
388fn parse_address(addr: &str, field: &str) -> Result<Address> {
389 Address::from_str(addr).map_err(|e| Error::bad_request(format!("Invalid {field} address: {e}")))
390}
391
392fn parse_bytes32(value: &str, field: &str) -> Result<FixedBytes<32>> {
393 FixedBytes::<32>::from_str(value)
394 .map_err(|e| Error::bad_request(format!("Invalid {field} bytes32: {e}")))
395}
396
397fn decimal_to_u256(d: Decimal, field: &str) -> Result<U256> {
398 let normalized = d.normalize();
399 if normalized.scale() != 0 {
400 return Err(Error::bad_request(format!("{field} must be an integer")));
401 }
402 let mantissa = normalized.mantissa();
403 if mantissa < 0 {
404 return Err(Error::bad_request(format!("{field} must be non-negative")));
405 }
406 Ok(U256::from(mantissa as u128))
407}
408
409fn order_side_to_u8(side: PolymarketOrderSide) -> u8 {
410 match side {
411 PolymarketOrderSide::Buy => 0,
412 PolymarketOrderSide::Sell => 1,
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use alloy_primitives::{Signature, keccak256};
419 use nautilus_core::hex;
420 use rstest::rstest;
421 use rust_decimal_macros::dec;
422 use ustr::Ustr;
423
424 use super::*;
425 use crate::common::enums::SignatureType;
426
427 const TEST_PRIVATE_KEY: &str =
428 "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
429
430 fn test_signer() -> OrderSigner {
431 let pk = EvmPrivateKey::new(TEST_PRIVATE_KEY).unwrap();
432 OrderSigner::new(&pk).unwrap()
433 }
434
435 const ZERO_BYTES32: &str = "0x0000000000000000000000000000000000000000000000000000000000000000";
436
437 fn test_order() -> PolymarketOrder {
438 PolymarketOrder {
439 salt: 123456789,
440 maker: "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".to_string(),
441 signer: "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".to_string(),
442 token_id: Ustr::from(
443 "71321045679252212594626385532706912750332728571942532289631379312455583992563",
444 ),
445 maker_amount: dec!(100000000),
446 taker_amount: dec!(50000000),
447 side: PolymarketOrderSide::Buy,
448 signature_type: SignatureType::Eoa,
449 expiration: "0".to_string(),
450 timestamp: "1713398400000".to_string(),
451 metadata: ZERO_BYTES32.to_string(),
452 builder: ZERO_BYTES32.to_string(),
453 signature: String::new(),
454 }
455 }
456
457 #[rstest]
458 fn test_order_typehash_matches_contract() {
459 let expected = keccak256(
461 "Order(uint256 salt,address maker,address signer,uint256 tokenId,uint256 makerAmount,uint256 takerAmount,uint8 side,uint8 signatureType,uint256 timestamp,bytes32 metadata,bytes32 builder)",
462 );
463 let order = test_order();
464 let eip712_order = build_eip712_order(&order).unwrap();
465 assert_eq!(eip712_order.eip712_type_hash(), expected);
466 }
467
468 #[rstest]
469 fn test_signer_address_derivation() {
470 let signer = test_signer();
471 let expected = Address::from_str("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266").unwrap();
473 assert_eq!(signer.address(), expected);
474 }
475
476 #[rstest]
477 fn test_sign_order_format() {
478 let signer = test_signer();
479 let order = test_order();
480
481 let sig = signer.sign_order(&order, false).unwrap();
482
483 assert!(sig.starts_with("0x"));
484 assert_eq!(sig.len(), 132); }
486
487 #[rstest]
488 fn test_sign_poly_1271_order_format() {
489 let signer = test_signer();
490 let mut order = test_order();
491 order.maker = "0x1111111111111111111111111111111111111111".to_string();
492 order.signer = order.maker.clone();
493 order.signature_type = SignatureType::Poly1271;
494
495 let sig = signer.sign_order(&order, false).unwrap();
496
497 assert!(sig.starts_with("0x"));
498 assert_eq!(sig.len(), 636);
499 }
500
501 #[rstest]
502 fn test_sign_poly_1271_order_requires_deposit_wallet_signer() {
503 let signer = test_signer();
504 let mut order = test_order();
505 order.maker = "0x1111111111111111111111111111111111111111".to_string();
506 order.signature_type = SignatureType::Poly1271;
507
508 let err = signer.sign_order(&order, false).unwrap_err();
509
510 assert!(
511 err.to_string()
512 .contains("maker and signer to both be the deposit wallet")
513 );
514 }
515
516 #[rstest]
517 fn test_sign_order_deterministic() {
518 let signer = test_signer();
519 let order = test_order();
520
521 let sig1 = signer.sign_order(&order, false).unwrap();
522 let sig2 = signer.sign_order(&order, false).unwrap();
523 assert_eq!(sig1, sig2);
524 }
525
526 #[rstest]
527 fn test_sign_order_neg_risk_differs() {
528 let signer = test_signer();
529 let order = test_order();
530
531 let sig_normal = signer.sign_order(&order, false).unwrap();
532 let sig_neg_risk = signer.sign_order(&order, true).unwrap();
533 assert_ne!(sig_normal, sig_neg_risk);
534 }
535
536 #[rstest]
537 fn test_sign_order_sell_side() {
538 let signer = test_signer();
539 let mut order = test_order();
540 let sig_buy = signer.sign_order(&order, false).unwrap();
541
542 order.side = PolymarketOrderSide::Sell;
543 let sig_sell = signer.sign_order(&order, false).unwrap();
544 assert_ne!(sig_buy, sig_sell);
545 }
546
547 #[rstest]
548 fn test_sign_order_different_amounts() {
549 let signer = test_signer();
550 let mut order = test_order();
551 let sig1 = signer.sign_order(&order, false).unwrap();
552
553 order.maker_amount = dec!(200000000);
554 let sig2 = signer.sign_order(&order, false).unwrap();
555 assert_ne!(sig1, sig2);
556 }
557
558 #[rstest]
559 fn test_build_eip712_order() {
560 let order = test_order();
561 let eip712 = build_eip712_order(&order).unwrap();
562
563 assert_eq!(eip712.salt, U256::from(123456789u64));
564 assert_eq!(
565 eip712.maker,
566 Address::from_str("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266").unwrap()
567 );
568 assert_eq!(eip712.makerAmount, U256::from(100000000u128));
569 assert_eq!(eip712.takerAmount, U256::from(50000000u128));
570 assert_eq!(eip712.side, 0); assert_eq!(eip712.signatureType, 0); assert_eq!(eip712.timestamp, U256::from(1713398400000u128));
573 assert_eq!(eip712.metadata, FixedBytes::<32>::ZERO);
574 assert_eq!(eip712.builder, FixedBytes::<32>::ZERO);
575 }
576
577 #[rstest]
578 fn test_build_eip712_order_with_builder_code() {
579 let mut order = test_order();
580 order.builder =
581 "0x0000000000000000000000000000000000000000000000000000000000000001".to_string();
582 let eip712 = build_eip712_order(&order).unwrap();
583
584 let mut expected = [0u8; 32];
585 expected[31] = 1;
586 assert_eq!(eip712.builder, FixedBytes::<32>::from(expected));
587 }
588
589 #[rstest]
590 fn test_decimal_to_u256_integer() {
591 let result = decimal_to_u256(dec!(100000000), "test").unwrap();
592 assert_eq!(result, U256::from(100000000u128));
593 }
594
595 #[rstest]
596 fn test_decimal_to_u256_zero() {
597 let result = decimal_to_u256(dec!(0), "test").unwrap();
598 assert_eq!(result, U256::ZERO);
599 }
600
601 #[rstest]
602 fn test_decimal_to_u256_rejects_fractional() {
603 let result = decimal_to_u256(dec!(100.5), "test");
604 assert!(result.is_err());
605 }
606
607 #[rstest]
608 fn test_decimal_to_u256_rejects_negative() {
609 let result = decimal_to_u256(dec!(-1), "test");
610 assert!(result.is_err());
611 }
612
613 #[rstest]
614 fn test_order_side_mapping() {
615 assert_eq!(order_side_to_u8(PolymarketOrderSide::Buy), 0);
616 assert_eq!(order_side_to_u8(PolymarketOrderSide::Sell), 1);
617 }
618
619 #[rstest]
620 fn test_contract_addresses_nonzero() {
621 assert_ne!(CTF_EXCHANGE, Address::ZERO);
622 assert_ne!(NEG_RISK_CTF_EXCHANGE, Address::ZERO);
623 assert_ne!(CTF_EXCHANGE, NEG_RISK_CTF_EXCHANGE);
624 }
625
626 #[rstest]
627 fn test_v2_contract_addresses_pinned() {
628 assert_eq!(
632 format!("{CTF_EXCHANGE:#x}"),
633 "0xe111180000d2663c0091e4f400237545b87b996b"
634 );
635 assert_eq!(
636 format!("{NEG_RISK_CTF_EXCHANGE:#x}"),
637 "0xe2222d279d744050d28e00520010520000310f59"
638 );
639 }
640
641 #[rstest]
642 fn test_domain_version_is_v2() {
643 assert_eq!(DOMAIN_VERSION, "2");
646 }
647
648 #[rstest]
649 fn test_sign_order_recoverable() {
650 let signer = test_signer();
651 let order = test_order();
652 let sig_hex = signer.sign_order(&order, false).unwrap();
653
654 let sig_bytes = hex::decode(&sig_hex[2..]).unwrap();
655 assert_eq!(sig_bytes.len(), 65);
656
657 let r = U256::from_be_slice(&sig_bytes[..32]);
658 let s = U256::from_be_slice(&sig_bytes[32..64]);
659 let v = sig_bytes[64];
660 let y_parity = v == 28;
661
662 let signature = Signature::new(r, s, y_parity);
663
664 let eip712_order = build_eip712_order(&order).unwrap();
665 let domain = eip712_domain! {
666 name: DOMAIN_NAME,
667 version: DOMAIN_VERSION,
668 chain_id: POLYGON_CHAIN_ID,
669 verifying_contract: CTF_EXCHANGE,
670 };
671 let signing_hash = eip712_order.eip712_signing_hash(&domain);
672
673 let recovered = signature
674 .recover_address_from_prehash(&signing_hash)
675 .unwrap();
676 assert_eq!(recovered, signer.address());
677 }
678
679 const PARITY_TOKEN_ID: &str =
686 "71321045679252212594626385532706912750332728571942532289631379312455583992563";
687
688 fn parity_order(
689 salt: u64,
690 side: PolymarketOrderSide,
691 signature_type: SignatureType,
692 maker_amount: Decimal,
693 taker_amount: Decimal,
694 timestamp: &str,
695 builder: &str,
696 ) -> PolymarketOrder {
697 PolymarketOrder {
698 salt,
699 maker: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string(),
700 signer: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string(),
701 token_id: Ustr::from(PARITY_TOKEN_ID),
702 maker_amount,
703 taker_amount,
704 side,
705 signature_type,
706 expiration: "0".to_string(),
707 timestamp: timestamp.to_string(),
708 metadata: ZERO_BYTES32.to_string(),
709 builder: builder.to_string(),
710 signature: String::new(),
711 }
712 }
713
714 #[rstest]
715 #[case::buy_standard_eoa(
716 parity_order(
717 123456789,
718 PolymarketOrderSide::Buy,
719 SignatureType::Eoa,
720 dec!(100000000),
721 dec!(50000000),
722 "1713398400000",
723 ZERO_BYTES32,
724 ),
725 false,
726 "0x32961c48ddac87ed3582f8e02097cd0eff4fcf80460306bd44b3710438dfa64c",
727 "0x89f178136333c8ebb32a19146cb891233e3202d474be6ef730c24dbc06ae4d2a0c99948a86d9b57de0f2c0bb8ec6964aa244ecf17cfa3a95b86878d0b64ad78a1b",
728 )]
729 #[case::sell_neg_risk_eoa(
730 parity_order(
731 987654321,
732 PolymarketOrderSide::Sell,
733 SignatureType::Eoa,
734 dec!(50000000),
735 dec!(100000000),
736 "1713398400000",
737 ZERO_BYTES32,
738 ),
739 true,
740 "0x8b878404bd92dea2bfea9975c9fcd816ec70a57ae431cb20d67bb773744aaef3",
741 "0xf7d60d64364e2615b08d9f69f3ea9afd3b4f83ecfbf05ddd3ca83f4916277fb97d2d080758d17c8e91c928aceb8ff252477ebaec051b672832500f63b4d36b061c",
742 )]
743 #[case::buy_with_builder_code_eoa(
744 parity_order(
745 1,
746 PolymarketOrderSide::Buy,
747 SignatureType::Eoa,
748 dec!(100000000),
749 dec!(50000000),
750 "1713398500000",
751 "0x0000000000000000000000000000000000000000000000000000000000000001",
752 ),
753 false,
754 "0x3df0b6f6ddfca837bc36964cae968b34ad35640b5d98f557c104da97e804e36a",
755 "0xf4d2b34659e8bc07a9572d40ee5a1639a1157409613b4c21566b1f33fd8fe11a364b3f306668cae7248ca7cdf72378f9266bc5628585aa939644400030671e081c",
756 )]
757 #[case::buy_poly_proxy(
758 parity_order(
763 111_111_111,
764 PolymarketOrderSide::Buy,
765 SignatureType::PolyProxy,
766 dec!(100000000),
767 dec!(50000000),
768 "1713398400000",
769 ZERO_BYTES32,
770 ),
771 false,
772 "0x8f88fe2fb3448f4b8ba639992029f0a47a01a14d15b5f2bf9833516571efd279",
773 "0x71a63c85b730cc934688f23ea6374afffef57a61690eba63dcb97a706c8a8d0f3d2a8e0280f3e252eb83be088ebae6b461a8eda18e559e079f59050b90057afa1c",
774 )]
775 #[case::sell_neg_risk_poly_gnosis_safe(
776 parity_order(
777 222_222_222,
778 PolymarketOrderSide::Sell,
779 SignatureType::PolyGnosisSafe,
780 dec!(50000000),
781 dec!(100000000),
782 "1713398400000",
783 ZERO_BYTES32,
784 ),
785 true,
786 "0xb34248702810a1d76580234a33f942a9801c3680de54cb3ef104572a8d482190",
787 "0xab9d33aee8b578fe5588c4a4b16bbef6fa05fc757020f95b98212e877a919e360a90296f02e97ecbabf3c200271ffe88eb9fd86912e8376cd27237bdad5f3abc1c",
788 )]
789 fn test_signature_matches_py_clob_client_v2(
790 #[case] order: PolymarketOrder,
791 #[case] neg_risk: bool,
792 #[case] expected_hash_hex: &str,
793 #[case] expected_signature_hex: &str,
794 ) {
795 let signer = test_signer();
796
797 let hash = order_hash(&order, neg_risk).unwrap();
798 assert_eq!(format!("{hash:#x}"), expected_hash_hex, "signing hash");
799
800 let signature = signer.sign_order(&order, neg_risk).unwrap();
801 assert_eq!(signature, expected_signature_hex, "signature");
802 }
803
804 #[rstest]
805 #[case::standard_exchange(
806 false,
807 "0x48cfd4c03dcee72230750e2dc5ea71048e91244c2a9fec2ed6ef790a74869596",
808 concat!(
809 "0x780beffb568c4510b8a135a92ca8071f124cf368fb0e902fe451c5f51e555791",
810 "0a2a87968caa08a242de85e83c27375fc09242e897aa77680f56622a54e6e7c",
811 "61c3264e159346253e26a64e00b69032db0e7d32f94628de3e6eecb50304d",
812 "7af3d2d3db4f9eed41f0490532a9460395d96602c6534a931bde4f0e3aad5",
813 "71e4f84a04f726465722875696e743235362073616c742c6164647265737320",
814 "6d616b65722c61646472657373207369676e65722c75696e7432353620746f",
815 "6b656e49642c75696e74323536206d616b6572416d6f756e742c75696e7432",
816 "35362074616b6572416d6f756e742c75696e743820736964652c75696e7438",
817 "207369676e6174757265547970652c75696e743235362074696d657374616d",
818 "702c62797465733332206d657461646174612c62797465733332206275696c",
819 "6465722900ba",
820 ),
821 )]
822 #[case::neg_risk_exchange(
823 true,
824 "0x82b94b9d570fdd7b07f517ea848606a9b74029f2387fbf07d8b9c856d652e608",
825 concat!(
826 "0xa411986b67c58113f8787ee54c41def9376d57ef4262b56438438710720604b7",
827 "00137e0175d7ae6afc40c2988179ad84b1b71a739f0974c8df3c12372f31b22c",
828 "1c9b858f53327b0bd13af8ec14cfb35234fb9eb7b0504d1a4e61f433840d3",
829 "0e81ad3db4f9eed41f0490532a9460395d96602c6534a931bde4f0e3aad571",
830 "e4f84a04f726465722875696e743235362073616c742c61646472657373206d",
831 "616b65722c61646472657373207369676e65722c75696e7432353620746f6b",
832 "656e49642c75696e74323536206d616b6572416d6f756e742c75696e743235",
833 "362074616b6572416d6f756e742c75696e743820736964652c75696e743820",
834 "7369676e6174757265547970652c75696e743235362074696d657374616d70",
835 "2c62797465733332206d657461646174612c62797465733332206275696c64",
836 "65722900ba",
837 ),
838 )]
839 fn test_poly_1271_signature_matches_py_clob_client_v2(
840 #[case] neg_risk: bool,
841 #[case] expected_hash: &str,
842 #[case] expected_signature: &str,
843 ) {
844 let signer = test_signer();
845 let mut order = parity_order(
846 333_333_333,
847 PolymarketOrderSide::Buy,
848 SignatureType::Poly1271,
849 dec!(100000000),
850 dec!(50000000),
851 "1713398400000",
852 ZERO_BYTES32,
853 );
854 order.maker = "0x1111111111111111111111111111111111111111".to_string();
855 order.signer = order.maker.clone();
856
857 let hash = order_hash(&order, neg_risk).unwrap();
858 assert_eq!(format!("{hash:#x}"), expected_hash, "signing hash");
859
860 let signature = signer.sign_order(&order, neg_risk).unwrap();
861 assert_eq!(signature, expected_signature, "signature");
862 }
863}