use crate::{constants, Error, Result};
use crate::types::{ChainId, Order as OrderData};
use alloy::primitives::{Address, U256, B256, keccak256};
use alloy::signers::{Signer as AlloySigner, local::PrivateKeySigner};
use alloy::sol;
use alloy::sol_types::{Eip712Domain, SolStruct};
sol! {
#[derive(Debug)]
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;
}
#[derive(Debug)]
struct Kernel {
bytes32 hash;
}
}
const KERNEL_DOMAIN_NAME: &str = "Kernel";
const KERNEL_DOMAIN_VERSION: &str = "0.3.1";
pub fn get_kernel_domain(chain_id: ChainId, kernel_address: Address) -> Eip712Domain {
Eip712Domain {
name: Some(KERNEL_DOMAIN_NAME.into()),
version: Some(KERNEL_DOMAIN_VERSION.into()),
chain_id: Some(U256::from(chain_id.as_u64())),
verifying_contract: Some(kernel_address),
salt: None,
}
}
pub fn build_kernel_wrapped_hash(order_hash: B256, kernel_domain: &Eip712Domain) -> B256 {
let wrapper = Kernel { hash: order_hash };
wrapper.eip712_signing_hash(kernel_domain)
}
pub async fn sign_order_for_predict_account(
order: &OrderData,
chain_id: ChainId,
verifying_contract: Address,
predict_account: Address,
ecdsa_validator_address: Address,
signer: &PrivateKeySigner,
) -> Result<String> {
let ctf_domain = get_domain(chain_id, verifying_contract);
let order_hash = build_typed_data_hash(order, &ctf_domain)?;
let kernel_domain = get_kernel_domain(chain_id, predict_account);
let wrapped_hash = build_kernel_wrapped_hash(order_hash, &kernel_domain);
let signature = signer
.sign_message(wrapped_hash.as_slice())
.await
.map_err(|e| Error::SigningError(format!("Failed to sign order for Predict Account: {}", e)))?;
let validator_bytes = ecdsa_validator_address.as_slice();
let mut signature_bytes = signature.as_bytes().to_vec();
if signature_bytes[64] < 27 {
signature_bytes[64] += 27;
}
let mut full_signature = Vec::with_capacity(1 + 20 + signature_bytes.len());
full_signature.push(0x01); full_signature.extend_from_slice(validator_bytes);
full_signature.extend_from_slice(&signature_bytes);
Ok(format!("0x{}", hex::encode(full_signature)))
}
pub async fn sign_message_for_predict_account(
message: &[u8],
chain_id: ChainId,
predict_account: Address,
ecdsa_validator_address: Address,
signer: &PrivateKeySigner,
) -> Result<String> {
let msg_len = message.len();
let mut prefixed = Vec::new();
prefixed.extend_from_slice(b"\x19Ethereum Signed Message:\n");
prefixed.extend_from_slice(msg_len.to_string().as_bytes());
prefixed.extend_from_slice(message);
let message_hash = keccak256(&prefixed);
let kernel_domain = get_kernel_domain(chain_id, predict_account);
let wrapped_hash = build_kernel_wrapped_hash(B256::from(message_hash), &kernel_domain);
let signature = signer
.sign_message(wrapped_hash.as_slice())
.await
.map_err(|e| Error::SigningError(format!("Failed to sign message for Predict Account: {}", e)))?;
let validator_bytes = ecdsa_validator_address.as_slice();
let mut signature_bytes = signature.as_bytes().to_vec();
if signature_bytes[64] < 27 {
signature_bytes[64] += 27;
}
let mut full_signature = Vec::with_capacity(1 + 20 + signature_bytes.len());
full_signature.push(0x01);
full_signature.extend_from_slice(validator_bytes);
full_signature.extend_from_slice(&signature_bytes);
Ok(format!("0x{}", hex::encode(full_signature)))
}
pub fn get_domain(chain_id: ChainId, verifying_contract: Address) -> Eip712Domain {
Eip712Domain {
name: Some(constants::PROTOCOL_NAME.into()),
version: Some(constants::PROTOCOL_VERSION.into()),
chain_id: Some(U256::from(chain_id.as_u64())),
verifying_contract: Some(verifying_contract),
salt: None,
}
}
fn order_to_sol_struct(order: &OrderData) -> Result<Order> {
Ok(Order {
salt: order.salt.parse::<U256>()
.map_err(|e| Error::InvalidOrderData(format!("Invalid salt: {}", e)))?,
maker: order.maker.parse::<Address>()
.map_err(|e| Error::InvalidOrderData(format!("Invalid maker address: {}", e)))?,
signer: order.signer.parse::<Address>()
.map_err(|e| Error::InvalidOrderData(format!("Invalid signer address: {}", e)))?,
taker: order.taker.parse::<Address>()
.map_err(|e| Error::InvalidOrderData(format!("Invalid taker address: {}", e)))?,
tokenId: order.token_id.parse::<U256>()
.map_err(|e| Error::InvalidOrderData(format!("Invalid token ID: {}", e)))?,
makerAmount: order.maker_amount.parse::<U256>()
.map_err(|e| Error::InvalidOrderData(format!("Invalid maker amount: {}", e)))?,
takerAmount: order.taker_amount.parse::<U256>()
.map_err(|e| Error::InvalidOrderData(format!("Invalid taker amount: {}", e)))?,
expiration: order.expiration.parse::<U256>()
.map_err(|e| Error::InvalidOrderData(format!("Invalid expiration: {}", e)))?,
nonce: order.nonce.parse::<U256>()
.map_err(|e| Error::InvalidOrderData(format!("Invalid nonce: {}", e)))?,
feeRateBps: order.fee_rate_bps.parse::<U256>()
.map_err(|e| Error::InvalidOrderData(format!("Invalid fee rate: {}", e)))?,
side: order.side as u8,
signatureType: order.signature_type as u8,
})
}
pub fn build_typed_data_hash(order: &OrderData, domain: &Eip712Domain) -> Result<B256> {
let sol_order = order_to_sol_struct(order)?;
Ok(sol_order.eip712_signing_hash(domain))
}
pub async fn sign_order(
order: &OrderData,
chain_id: ChainId,
verifying_contract: Address,
signer: &PrivateKeySigner,
) -> Result<String> {
let domain = get_domain(chain_id, verifying_contract);
let hash = build_typed_data_hash(order, &domain)?;
let signature = signer
.sign_hash(&hash)
.await
.map_err(|e| Error::SigningError(format!("Failed to sign order: {}", e)))?;
let mut sig_bytes = signature.as_bytes().to_vec();
if sig_bytes[64] < 27 {
sig_bytes[64] += 27;
}
Ok(format!("0x{}", hex::encode(sig_bytes)))
}
#[allow(dead_code)]
pub fn get_order_hash(
order: &OrderData,
chain_id: ChainId,
verifying_contract: Address,
) -> Result<String> {
let domain = get_domain(chain_id, verifying_contract);
let hash = build_typed_data_hash(order, &domain)?;
Ok(format!("0x{}", hex::encode(hash.as_slice())))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{Side, SignatureType};
#[test]
fn test_get_domain() {
let verifying_contract: Address = "0x8BC070BEdAB741406F4B1Eb65A72bee27894B689"
.parse()
.unwrap();
let domain = get_domain(ChainId::BnbMainnet, verifying_contract);
assert_eq!(domain.name, Some(constants::PROTOCOL_NAME.into()));
assert_eq!(domain.version, Some(constants::PROTOCOL_VERSION.into()));
assert_eq!(domain.chain_id, Some(U256::from(56)));
assert_eq!(domain.verifying_contract, Some(verifying_contract));
}
#[test]
fn test_order_to_sol_struct() {
let order = OrderData {
salt: "12345".to_string(),
maker: "0x8BC070BEdAB741406F4B1Eb65A72bee27894B689".to_string(),
signer: "0x8BC070BEdAB741406F4B1Eb65A72bee27894B689".to_string(),
taker: "0x0000000000000000000000000000000000000000".to_string(),
token_id: "67890".to_string(),
maker_amount: "1000000000000000000".to_string(),
taker_amount: "2000000000000000000".to_string(),
expiration: "1700000000".to_string(),
nonce: "0".to_string(),
fee_rate_bps: "100".to_string(),
side: Side::Buy,
signature_type: SignatureType::Eoa,
};
let sol_order = order_to_sol_struct(&order).unwrap();
assert_eq!(sol_order.salt, U256::from(12345));
assert_eq!(sol_order.side, 0);
assert_eq!(sol_order.signatureType, 0);
}
}