polyte_clob/core/
eip712.rs1use alloy::{
2 primitives::{keccak256, Address, U256},
3 signers::Signer as AlloySigner,
4 sol,
5 sol_types::SolStruct,
6};
7
8use crate::{
9 core::chain::Chain,
10 error::ClobError,
11 types::{Order, SignatureType},
12};
13
14sol! {
15 #[derive(Debug, PartialEq, Eq)]
16 struct EIP712Domain {
17 string name;
18 string version;
19 uint256 chainId;
20 address verifyingContract;
21 }
22
23 #[derive(Debug, PartialEq, Eq)]
24 struct OrderStruct {
25 uint256 salt;
26 address maker;
27 address signer;
28 address taker;
29 uint256 tokenId;
30 uint256 makerAmount;
31 uint256 takerAmount;
32 uint256 expiration;
33 uint256 nonce;
34 uint256 feeRateBps;
35 uint8 side;
36 uint8 signatureType;
37 }
38}
39
40pub async fn sign_order<S: AlloySigner>(
42 order: &Order,
43 signer: &S,
44 chain_id: u64,
45) -> Result<String, ClobError> {
46 let chain = Chain::from_chain_id(chain_id)
47 .ok_or_else(|| ClobError::Crypto(format!("Unsupported chain ID: {}", chain_id)))?;
48 let contracts = chain.contracts();
49
50 let domain = EIP712Domain {
52 name: "Polymarket CTF Exchange".to_string(),
53 version: "1".to_string(),
54 chainId: U256::from(chain_id),
55 verifyingContract: contracts.neg_risk_exchange,
56 };
57
58 let order_struct = OrderStruct {
60 salt: U256::from_str_radix(&order.salt, 10)
61 .map_err(|e| ClobError::Crypto(format!("Invalid salt: {}", e)))?,
62 maker: order.maker,
63 signer: order.signer,
64 taker: order.taker,
65 tokenId: U256::from_str_radix(&order.token_id, 10)
66 .map_err(|e| ClobError::Crypto(format!("Invalid token_id: {}", e)))?,
67 makerAmount: U256::from_str_radix(&order.maker_amount, 10)
68 .map_err(|e| ClobError::Crypto(format!("Invalid maker_amount: {}", e)))?,
69 takerAmount: U256::from_str_radix(&order.taker_amount, 10)
70 .map_err(|e| ClobError::Crypto(format!("Invalid taker_amount: {}", e)))?,
71 expiration: U256::from_str_radix(&order.expiration, 10)
72 .map_err(|e| ClobError::Crypto(format!("Invalid expiration: {}", e)))?,
73 nonce: U256::from_str_radix(&order.nonce, 10)
74 .map_err(|e| ClobError::Crypto(format!("Invalid nonce: {}", e)))?,
75 feeRateBps: U256::from_str_radix(&order.fee_rate_bps, 10)
76 .map_err(|e| ClobError::Crypto(format!("Invalid fee_rate_bps: {}", e)))?,
77 side: match order.side {
78 crate::types::OrderSide::Buy => 0,
79 crate::types::OrderSide::Sell => 1,
80 },
81 signatureType: match order.signature_type {
82 SignatureType::Eoa => 0,
83 SignatureType::PolyProxy => 1,
84 SignatureType::PolyGnosisSafe => 2,
85 },
86 };
87
88 let struct_hash = keccak256(order_struct.eip712_hash_struct());
90
91 let domain_separator = keccak256(domain.eip712_hash_struct());
93
94 let mut message = Vec::new();
96 message.extend_from_slice(b"\x19\x01");
97 message.extend_from_slice(domain_separator.as_slice());
98 message.extend_from_slice(struct_hash.as_slice());
99 let digest = keccak256(&message);
100
101 let signature = signer.sign_hash(&digest).await?;
103
104 Ok(format!("0x{}", hex::encode(signature.as_bytes())))
105}
106
107pub async fn sign_clob_auth<S: AlloySigner>(
109 signer: &S,
110 chain_id: u64,
111 timestamp: u64,
112 nonce: u32,
113) -> Result<String, ClobError> {
114 sol! {
115 #[derive(Debug, PartialEq, Eq)]
116 struct ClobAuth {
117 string message;
118 }
119 }
120
121 let domain = EIP712Domain {
122 name: "ClobAuthDomain".to_string(),
123 version: "1".to_string(),
124 chainId: U256::from(chain_id),
125 verifyingContract: Address::ZERO,
126 };
127
128 let message = format!(
129 "This message attests that I control the given wallet\ntimestamp: {}\nnonce: {}",
130 timestamp, nonce
131 );
132
133 let clob_auth = ClobAuth { message };
134
135 let struct_hash = keccak256(clob_auth.eip712_hash_struct());
137
138 let domain_separator = keccak256(domain.eip712_hash_struct());
140
141 let mut digest_message = Vec::new();
143 digest_message.extend_from_slice(b"\x19\x01");
144 digest_message.extend_from_slice(domain_separator.as_slice());
145 digest_message.extend_from_slice(struct_hash.as_slice());
146 let digest = keccak256(&digest_message);
147
148 let signature = signer.sign_hash(&digest).await?;
150
151 Ok(format!("0x{}", hex::encode(signature.as_bytes())))
152}