use crate::{
AgentCheckResponse, Error, EvidenceResponse, UsageResponse, ValidateRequest, ValidateResponse,
};
use reqwest::Client;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct ProofGateConfig {
pub api_key: String,
pub base_url: String,
pub chain_id: u64,
pub guardrail_id: Option<String>,
pub timeout: Duration,
}
impl Default for ProofGateConfig {
fn default() -> Self {
Self {
api_key: String::new(),
base_url: "https://www.proofgate.xyz/api".to_string(),
chain_id: 8453,
guardrail_id: None,
timeout: Duration::from_secs(30),
}
}
}
pub struct ProofGate {
config: ProofGateConfig,
client: Client,
}
impl ProofGate {
pub fn new(api_key: &str) -> Result<Self, Error> {
Self::with_config(ProofGateConfig {
api_key: api_key.to_string(),
..Default::default()
})
}
pub fn with_config(config: ProofGateConfig) -> Result<Self, Error> {
if config.api_key.is_empty() {
return Err(Error::MissingApiKey);
}
if !config.api_key.starts_with("pg_") {
return Err(Error::InvalidApiKey);
}
let client = Client::builder()
.timeout(config.timeout)
.build()
.map_err(Error::NetworkError)?;
Ok(Self { config, client })
}
pub async fn validate(&self, request: ValidateRequest) -> Result<ValidateResponse, Error> {
let url = format!("{}/validate", self.config.base_url);
let mut body = serde_json::json!({
"from": request.from,
"to": request.to,
"data": request.data,
"value": request.value.unwrap_or_else(|| "0".to_string()),
"chainId": request.chain_id.unwrap_or(self.config.chain_id),
});
if let Some(guardrail_id) = request.guardrail_id.or(self.config.guardrail_id.clone()) {
body["guardrailId"] = serde_json::Value::String(guardrail_id);
}
let response = self
.client
.post(&url)
.header("Content-Type", "application/json")
.header("X-API-Key", &self.config.api_key)
.json(&body)
.send()
.await?;
let status = response.status();
if !status.is_success() {
let error_body: serde_json::Value = response.json().await.unwrap_or_default();
return Err(Error::ApiError {
status: status.as_u16(),
message: error_body["error"]
.as_str()
.or(error_body["message"].as_str())
.unwrap_or("Unknown error")
.to_string(),
});
}
Ok(response.json().await?)
}
pub async fn validate_or_throw(
&self,
request: ValidateRequest,
) -> Result<ValidateResponse, Error> {
let result = self.validate(request).await?;
if !result.safe {
return Err(Error::ValidationFailed {
reason: result.reason.clone(),
result: Box::new(result),
});
}
Ok(result)
}
pub async fn check_agent(&self, wallet: &str) -> Result<AgentCheckResponse, Error> {
let url = format!("{}/agents/check?wallet={}", self.config.base_url, wallet);
let response = self
.client
.get(&url)
.header("X-API-Key", &self.config.api_key)
.send()
.await?;
let status = response.status();
if !status.is_success() {
let error_body: serde_json::Value = response.json().await.unwrap_or_default();
return Err(Error::ApiError {
status: status.as_u16(),
message: error_body["error"]
.as_str()
.unwrap_or("Unknown error")
.to_string(),
});
}
Ok(response.json().await?)
}
pub async fn get_evidence(&self, validation_id: &str) -> Result<EvidenceResponse, Error> {
let url = format!("{}/evidence/{}", self.config.base_url, validation_id);
let response = self
.client
.get(&url)
.header("X-API-Key", &self.config.api_key)
.send()
.await?;
let status = response.status();
if !status.is_success() {
let error_body: serde_json::Value = response.json().await.unwrap_or_default();
return Err(Error::ApiError {
status: status.as_u16(),
message: error_body["error"]
.as_str()
.unwrap_or("Unknown error")
.to_string(),
});
}
Ok(response.json().await?)
}
pub async fn get_usage(&self, wallet: &str) -> Result<UsageResponse, Error> {
let url = format!("{}/validate?wallet={}", self.config.base_url, wallet);
let response = self
.client
.get(&url)
.header("X-API-Key", &self.config.api_key)
.send()
.await?;
let status = response.status();
if !status.is_success() {
let error_body: serde_json::Value = response.json().await.unwrap_or_default();
return Err(Error::ApiError {
status: status.as_u16(),
message: error_body["error"]
.as_str()
.unwrap_or("Unknown error")
.to_string(),
});
}
Ok(response.json().await?)
}
}
pub async fn is_transaction_safe(
api_key: &str,
from: &str,
to: &str,
data: &str,
) -> Result<bool, Error> {
let pg = ProofGate::new(api_key)?;
let result = pg
.validate(ValidateRequest {
from: from.to_string(),
to: to.to_string(),
data: data.to_string(),
value: None,
guardrail_id: None,
chain_id: None,
})
.await?;
Ok(result.safe)
}