use super::BuilderConfig;
use base64::{engine::general_purpose, Engine};
use crate::error::{RelayerError, Result};
use hmac::{Hmac, Mac};
use reqwest::header::{HeaderMap, HeaderValue};
use sha2::Sha256;
use std::time::{SystemTime, UNIX_EPOCH};
type HmacSha256 = Hmac<Sha256>;
pub fn build_hmac_signature(
secret: &str,
timestamp: &str,
method: &str,
path: &str,
body: &str,
) -> Result<String> {
let decoded_secret = general_purpose::STANDARD
.decode(secret)
.or_else(|_| general_purpose::URL_SAFE.decode(secret))
.or_else(|_| general_purpose::URL_SAFE_NO_PAD.decode(secret))
.map_err(|e| RelayerError::AuthError(format!("Failed to decode base64 secret: {e}")))?;
let message = format!("{}{}{}{}", timestamp, method, path, body);
let mut mac = HmacSha256::new_from_slice(&decoded_secret)
.map_err(|e| RelayerError::AuthError(format!("HMAC key error: {e}")))?;
mac.update(message.as_bytes());
let result = mac.finalize().into_bytes();
let b64 = general_purpose::STANDARD.encode(result);
let url_safe = b64.replace('+', "-").replace('/', "_");
Ok(url_safe)
}
pub fn build_headers(
config: &BuilderConfig,
method: &str,
path: &str,
body: &str,
) -> Result<HeaderMap> {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|_| RelayerError::AuthError("System time before UNIX EPOCH".to_string()))?
.as_secs();
let timestamp_str = timestamp.to_string();
let signature = build_hmac_signature(&config.secret, ×tamp_str, method, path, body)?;
let mut headers = HeaderMap::new();
headers.insert(
"POLY_BUILDER_API_KEY",
HeaderValue::from_str(&config.key)
.map_err(|_| RelayerError::AuthError("Invalid key header value".to_string()))?,
);
headers.insert(
"POLY_BUILDER_TIMESTAMP",
HeaderValue::from_str(×tamp_str)
.map_err(|_| RelayerError::AuthError("Invalid timestamp header value".to_string()))?,
);
headers.insert(
"POLY_BUILDER_PASSPHRASE",
HeaderValue::from_str(&config.passphrase)
.map_err(|_| RelayerError::AuthError("Invalid passphrase header value".to_string()))?,
);
headers.insert(
"POLY_BUILDER_SIGNATURE",
HeaderValue::from_str(&signature)
.map_err(|_| RelayerError::AuthError("Invalid signature header value".to_string()))?,
);
Ok(headers)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hmac_signature_matches_reference() {
let secret = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
let timestamp = "1000000";
let method = "test-sign";
let path = "/orders";
let body = r#"{"hash": "0x123"}"#;
let sig = build_hmac_signature(secret, timestamp, method, path, body).unwrap();
assert_eq!(sig, "ZwAdJKvoYRlEKDkNMwd5BuwNNtg93kNaR_oU2HrfVvc=");
}
}