use crate::error::{Error, Result};
use super::constants::{CHAIN_ID, FEE_ESCROW_ADDRESS};
use super::signer::TradingSigner;
use super::types::{Eip712Payload, FeeAuthRequest};
use alloy_primitives::U256;
const ZERO_ADDRESS: &str = "0x0000000000000000000000000000000000000000";
pub fn calculate_fee(price: f64, size: f64, fee_bps: u16) -> u64 {
let notional_raw = (price * size * 1e6).floor() as i64;
let fee = (notional_raw as f64 * fee_bps as f64 / 10000.0).floor() as i64;
if fee > 0 { fee as u64 } else { 0 }
}
pub fn generate_escrow_order_id() -> String {
use rand::Rng;
let mut rng = rand::thread_rng();
let bytes: [u8; 32] = rng.gen();
format!("0x{}", hex::encode(bytes))
}
pub async fn fetch_escrow_nonce(rpc_url: &str, signer_address: &str) -> Result<u64> {
let client = reqwest::Client::new();
let addr = signer_address.to_lowercase().replace("0x", "");
let data = format!("0x2d0335ab{:0>64}", addr);
let resp = client
.post(rpc_url)
.json(&serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "eth_call",
"params": [{"to": FEE_ESCROW_ADDRESS, "data": data}, "latest"]
}))
.send()
.await
.map_err(|e| Error::Trading(format!("Escrow nonce fetch failed: {}", e)))?;
let json: serde_json::Value = resp.json().await
.map_err(|e| Error::Trading(format!("Escrow nonce parse failed: {}", e)))?;
if let Some(err) = json.get("error") {
return Err(Error::Trading(format!("Escrow nonce RPC error: {}", err)));
}
let hex = json.get("result").and_then(|v| v.as_str()).unwrap_or("0x0");
let nonce = u64::from_str_radix(hex.trim_start_matches("0x"), 16)
.map_err(|e| Error::Trading(format!("Escrow nonce parse int failed: {}", e)))?;
Ok(nonce)
}
pub async fn sign_fee_auth(
signer: &dyn TradingSigner,
escrow_order_id: &str,
payer: &str,
fee_amount: u64,
deadline: u64,
nonce: u64,
affiliate: Option<&str>,
affiliate_share_bps: Option<u16>,
) -> Result<FeeAuthRequest> {
let signer_address = format!("{}", signer.address());
let payload = Eip712Payload {
domain: serde_json::json!({
"name": "PolyNodeFeeEscrow",
"version": "1",
"chainId": CHAIN_ID,
"verifyingContract": FEE_ESCROW_ADDRESS
}),
types: serde_json::json!({
"FeeAuth": [
{"name": "orderId", "type": "bytes32"},
{"name": "payer", "type": "address"},
{"name": "signer", "type": "address"},
{"name": "feeAmount", "type": "uint256"},
{"name": "deadline", "type": "uint256"},
{"name": "nonce", "type": "uint256"}
]
}),
primary_type: "FeeAuth".into(),
message: serde_json::json!({
"orderId": escrow_order_id,
"payer": payer,
"signer": signer_address,
"feeAmount": fee_amount.to_string(),
"deadline": deadline.to_string(),
"nonce": nonce.to_string()
}),
};
let sig_bytes = signer.sign_typed_data(&payload).await?;
let signature = format!("0x{}", hex::encode(&sig_bytes));
Ok(FeeAuthRequest {
escrow_order_id: escrow_order_id.to_string(),
payer: payer.to_string(),
signer: signer_address,
fee_amount: fee_amount.to_string(),
deadline,
nonce,
signature,
affiliate: affiliate.unwrap_or(ZERO_ADDRESS).to_string(),
affiliate_share_bps: affiliate_share_bps.unwrap_or(10000),
})
}