1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
use alloy_primitives::{Address, U256, keccak256, Signature};
use alloy_signer::Signer;
use alloy_signer_local::PrivateKeySigner;
use alloy_sol_types::{eip712_domain, Eip712Domain, SolStruct};
use crate::{
constants::{PROTOCOL_NAME, PROTOCOL_VERSION},
error::{OrderError, Result},
models::{Order, OrderData, SignedOrder},
types::SignatureType,
utils::generate_order_salt,
};
/// Builder for creating and signing Polymarket exchange orders
pub struct ExchangeOrderBuilder {
/// Contract address for EIP-712 domain
contract_address: Address,
/// Chain ID for EIP-712 domain
chain_id: u64,
/// Signer for signing orders
signer: PrivateKeySigner,
/// EIP-712 domain separator
domain: Eip712Domain,
/// Custom salt generator function (optional)
/// Defaults to generate_order_salt if not provided
salt_generator: Option<Box<dyn Fn() -> U256 + Send + Sync>>,
}
impl ExchangeOrderBuilder {
/// Create a new ExchangeOrderBuilder
///
/// This matches TypeScript's constructor which accepts an optional salt generator
///
/// # Arguments
/// * `contract_address` - The exchange contract address
/// * `chain_id` - The blockchain chain ID
/// * `signer` - The private key signer
/// * `salt_generator` - Optional custom salt generator function (defaults to `generate_order_salt`)
pub fn new(
contract_address: Address,
chain_id: u64,
signer: PrivateKeySigner,
salt_generator: Option<Box<dyn Fn() -> U256 + Send + Sync>>,
) -> Self {
// Create EIP-712 domain
let domain = eip712_domain! {
name: PROTOCOL_NAME,
version: PROTOCOL_VERSION,
chain_id: chain_id,
verifying_contract: contract_address,
};
Self {
contract_address,
chain_id,
signer,
domain,
salt_generator,
}
}
/// Build and sign an order in one step
///
/// This is the main entry point, equivalent to TypeScript's `buildSignedOrder`
pub async fn build_signed_order(&self, order_data: OrderData) -> Result<SignedOrder> {
// Step 1: Build the order object
let order = self.build_order(order_data).await?;
// Step 2: Sign the order using EIP-712
let signature = self.sign_order(&order).await?;
// Step 3: Return the signed order
Ok(SignedOrder::new(order, signature.as_bytes().to_vec()))
}
/// Build an Order object from OrderData
///
/// Equivalent to TypeScript's `buildOrder` method
pub async fn build_order(&self, order_data: OrderData) -> Result<Order> {
// Determine signer address (defaults to maker if not specified)
let signer_address = order_data.signer.unwrap_or(order_data.maker);
// Verify that the signer matches the wallet's address
let wallet_address = self.signer.address();
if signer_address != wallet_address {
return Err(OrderError::SignerMismatch {
expected: format!("{:?}", signer_address),
actual: format!("{:?}", wallet_address),
});
}
// Set default expiration (0 = no expiration)
let expiration = order_data.expiration.unwrap_or(U256::ZERO);
// Set default signature type (EOA)
let signature_type = order_data.signature_type.unwrap_or(SignatureType::Eoa);
// Generate salt for uniqueness using custom generator or default
let salt = match &self.salt_generator {
Some(generator) => generator(),
None => generate_order_salt(),
};
Ok(Order {
salt,
maker: order_data.maker,
signer: signer_address,
taker: order_data.taker,
tokenId: order_data.token_id, // Now U256 instead of String
makerAmount: order_data.maker_amount,
takerAmount: order_data.taker_amount,
expiration,
nonce: order_data.nonce,
feeRateBps: order_data.fee_rate_bps,
side: order_data.side as u8,
signatureType: signature_type as u8,
})
}
/// Sign an order using EIP-712
///
/// Equivalent to TypeScript's `buildOrderSignature` method
pub async fn sign_order(&self, order: &Order) -> Result<Signature> {
// Calculate EIP-712 hash
let hash = self.build_order_hash(order);
// Sign the hash
self.signer
.sign_hash(&hash.into())
.await
.map_err(|e| OrderError::SigningError(e.to_string()))
}
/// Calculate the EIP-712 hash of an order
///
/// Equivalent to TypeScript's `buildOrderHash` method
pub fn build_order_hash(&self, order: &Order) -> [u8; 32] {
// Get the struct hash
let struct_hash = order.eip712_hash_struct();
// Compute EIP-712 hash: keccak256("\x19\x01" ‖ domainSeparator ‖ structHash)
let domain_separator = self.domain.hash_struct();
let mut data = Vec::with_capacity(66);
data.extend_from_slice(&[0x19, 0x01]);
data.extend_from_slice(domain_separator.as_slice());
data.extend_from_slice(struct_hash.as_slice());
keccak256(&data).into()
}
/// Get the signer address
pub fn signer_address(&self) -> Address {
self.signer.address()
}
/// Get the contract address
pub fn contract_address(&self) -> Address {
self.contract_address
}
/// Get the chain ID
pub fn chain_id(&self) -> u64 {
self.chain_id
}
}