use crate::clob::{
get_contract_config,
error::{ClobError, Result},
types::{SignedOrderArgs, PartialOrderArgs},
Signer,
};
use alloy::primitives::{Address, U256};
use alloy::sol;
use alloy_sol_types::SolStruct;
sol! {
struct Order {
uint256 salt;
address maker;
address signer;
address taker;
uint256 tokenId;
uint256 makerAmount;
uint256 takerAmount;
uint256 expiration;
uint256 nonce;
uint256 feeRateBps;
uint8 side;
uint8 signatureType;
}
}
const DOMAIN_NAME: &str = "Polymarket CTF Exchange";
const DOMAIN_VERSION: &str = "1";
pub async fn sign_order(
partial: PartialOrderArgs,
signer: &Signer,
neg_risk: bool,
) -> Result<SignedOrderArgs> {
let maker_amount = parse_amount(&partial.maker_amount)?;
let taker_amount = parse_amount(&partial.taker_amount)?;
let token_id = parse_token_id(&partial.token_id)?;
let order = Order {
salt: U256::from(partial.salt),
maker: parse_address(&partial.maker)?,
signer: parse_address(&partial.signer)?,
taker: parse_address(&partial.taker)?,
tokenId: token_id,
makerAmount: maker_amount,
takerAmount: taker_amount,
expiration: U256::from(partial.expiration),
nonce: U256::from(partial.nonce),
feeRateBps: U256::from(partial.fee_rate_bps),
side: partial.side as u8,
signatureType: partial.signature_type as u8,
};
let contract_config = get_contract_config(signer.chain_id(), neg_risk)
.ok_or_else(|| {
ClobError::InvalidOrder(format!("Unsupported chain ID: {}", signer.chain_id()))
})?;
let domain = alloy::sol_types::eip712_domain! {
name: DOMAIN_NAME,
version: DOMAIN_VERSION,
chain_id: signer.chain_id(),
verifying_contract: parse_address(&contract_config.exchange)?,
};
let signing_hash = order.eip712_signing_hash(&domain);
let signature = signer.sign_hash(signing_hash.as_ref()).await?;
Ok(SignedOrderArgs {
salt: partial.salt,
maker: partial.maker,
signer: partial.signer,
taker: partial.taker,
token_id: partial.token_id,
maker_amount: partial.maker_amount,
taker_amount: partial.taker_amount,
side: partial.side,
fee_rate_bps: partial.fee_rate_bps,
nonce: partial.nonce,
signature_type: partial.signature_type,
expiration: partial.expiration,
signature,
})
}
fn parse_amount(amount: &str) -> Result<U256> {
U256::from_str_radix(amount, 10).map_err(|e| {
ClobError::InvalidOrder(format!("Invalid amount '{}': {}", amount, e))
})
}
fn parse_token_id(token_id: &str) -> Result<U256> {
if let Some(hex) = token_id.strip_prefix("0x") {
U256::from_str_radix(hex, 16)
} else {
U256::from_str_radix(token_id, 10)
}
.map_err(|e| ClobError::InvalidOrder(format!("Invalid token ID '{}': {}", token_id, e)))
}
fn parse_address(addr: &str) -> Result<Address> {
addr.parse()
.map_err(|e| ClobError::InvalidOrder(format!("Invalid address '{}': {}", addr, e)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_amount() {
let amount = parse_amount("1000000").unwrap();
assert_eq!(amount, U256::from(1000000));
}
#[test]
fn test_parse_token_id_decimal() {
let token_id = parse_token_id("123456").unwrap();
assert_eq!(token_id, U256::from(123456));
}
#[test]
fn test_parse_token_id_hex() {
let token_id = parse_token_id("0x1e240").unwrap();
assert_eq!(token_id, U256::from(123456));
}
#[test]
fn test_parse_address() {
let addr = parse_address("0x0000000000000000000000000000000000000000").unwrap();
assert_eq!(
addr,
"0x0000000000000000000000000000000000000000"
.parse::<Address>()
.unwrap()
);
}
#[test]
fn test_invalid_amount() {
assert!(parse_amount("not_a_number").is_err());
}
#[test]
fn test_invalid_address() {
assert!(parse_address("not_an_address").is_err());
}
}