use crate::constants::{CLOB_DOMAIN_NAME, CLOB_VERSION, MSG_TO_SIGN};
use crate::errors::{ClobError, ClobResult};
use alloy_primitives::{keccak256, Address, B256, U256};
use alloy_signer::Signer;
use alloy_signer_local::PrivateKeySigner;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ClobAuth {
pub address: Address,
pub timestamp: String,
pub nonce: U256,
pub message: String,
}
impl ClobAuth {
const TYPE_STRING: &'static str =
"ClobAuth(address address,string timestamp,uint256 nonce,string message)";
fn domain_separator(chain_id: u64) -> B256 {
let domain_type_hash =
keccak256(b"EIP712Domain(string name,string version,uint256 chainId)");
let name_hash = keccak256(CLOB_DOMAIN_NAME.as_bytes());
let version_hash = keccak256(CLOB_VERSION.as_bytes());
let mut encoded = Vec::new();
encoded.extend_from_slice(domain_type_hash.as_slice());
encoded.extend_from_slice(name_hash.as_slice());
encoded.extend_from_slice(version_hash.as_slice());
let chain_id_u256 = U256::from(chain_id);
encoded.extend_from_slice(&chain_id_u256.to_be_bytes::<32>());
keccak256(&encoded)
}
fn struct_hash(&self) -> B256 {
let type_hash = keccak256(Self::TYPE_STRING.as_bytes());
let timestamp_hash = keccak256(self.timestamp.as_bytes());
let message_hash = keccak256(self.message.as_bytes());
let mut encoded = Vec::new();
encoded.extend_from_slice(type_hash.as_slice());
let mut address_bytes = [0u8; 32];
address_bytes[12..].copy_from_slice(self.address.as_slice());
encoded.extend_from_slice(&address_bytes);
encoded.extend_from_slice(timestamp_hash.as_slice());
encoded.extend_from_slice(&self.nonce.to_be_bytes::<32>());
encoded.extend_from_slice(message_hash.as_slice());
keccak256(&encoded)
}
fn eip712_hash(&self, chain_id: u64) -> B256 {
let domain_separator = Self::domain_separator(chain_id);
let struct_hash = self.struct_hash();
let mut message = Vec::new();
message.push(0x19);
message.push(0x01);
message.extend_from_slice(domain_separator.as_slice());
message.extend_from_slice(struct_hash.as_slice());
keccak256(&message)
}
}
pub async fn build_clob_eip712_signature(
wallet: &PrivateKeySigner,
chain_id: u64,
timestamp: u64,
nonce: u64,
) -> ClobResult<String> {
let address = wallet.address();
let clob_auth = ClobAuth {
address,
timestamp: timestamp.to_string(),
nonce: U256::from(nonce),
message: MSG_TO_SIGN.to_string(),
};
let message_hash = clob_auth.eip712_hash(chain_id);
let signature = wallet
.sign_hash(&message_hash)
.await
.map_err(|e| ClobError::SigningError(e.to_string()))?;
Ok(format!("0x{}", hex::encode(signature.as_bytes())))
}