agentswap 0.1.0

AgentSwap CLI — AI-agent-optimized DEX aggregator
// HTTP client module for AgentSwap API calls.
// Exports: Client.
// Deps: reqwest, serde_json, eyre, crate::routes.
mod debug;
mod transport;

use eyre::Result;

/// Thin HTTP client for the sr-service API.
pub struct Client {
    http: reqwest::Client,
    base_url: String,
    api_key: Option<String>,
}

#[allow(dead_code)]
impl Client {
    pub fn new(base_url: &str, api_key: Option<String>) -> Self {
        Self {
            http: reqwest::Client::new(),
            base_url: base_url.trim_end_matches('/').to_string(),
            api_key,
        }
    }

    pub fn api_key(&self) -> Option<String> {
        self.api_key.clone()
    }

    // --- Consumer endpoints ---

    pub async fn quote(&self, body: &serde_json::Value) -> Result<serde_json::Value> {
        self.post(crate::routes::QUOTE, body).await
    }

    pub async fn health(&self) -> Result<serde_json::Value> {
        self.get(crate::routes::HEALTH).await
    }

    pub async fn tokens(&self) -> Result<serde_json::Value> {
        self.get(crate::routes::TOKENS).await
    }

    pub async fn pool(&self, chain_id: u64, address: &str) -> Result<serde_json::Value> {
        self.get(&crate::routes::pool(chain_id, address)).await
    }

    pub async fn challenge(&self, address: &str) -> Result<serde_json::Value> {
        self.post(
            crate::routes::AUTH_CHALLENGE,
            &serde_json::json!({ "address": address }),
        )
        .await
    }

    pub async fn register(
        &self,
        address: &str,
        nonce: &str,
        signature: &str,
    ) -> Result<serde_json::Value> {
        self.post(
            crate::routes::AUTH_REGISTER,
            &serde_json::json!({
                "address": address,
                "nonce": nonce,
                "signature": signature,
            }),
        )
        .await
    }

    pub async fn key_info(&self) -> Result<serde_json::Value> {
        self.get(crate::routes::AUTH_KEY_INFO).await
    }

    pub async fn pricing(&self) -> Result<serde_json::Value> {
        self.get(crate::routes::AUTH_PRICING).await
    }

    pub async fn claim_quota(&self, chain_id: u64, tx_hash: &str) -> Result<serde_json::Value> {
        self.post(
            crate::routes::AUTH_QUOTA_CLAIM,
            &serde_json::json!({
                "chain_id": chain_id,
                "tx_hash": tx_hash,
            }),
        )
        .await
    }

    // --- Admin/investigation endpoints ---

    pub async fn status(&self) -> Result<serde_json::Value> {
        self.get(crate::routes::STATUS).await
    }

    pub async fn discovery_stats(&self) -> Result<serde_json::Value> {
        self.get("/admin/discovery-stats").await
    }

    pub async fn discover_slot(&self, token: &str, chain: &str) -> Result<serde_json::Value> {
        let mut url = format!("{}{}", self.base_url, crate::routes::DISCOVER_SLOT);
        let mut params = vec![format!("token={token}")];
        if !chain.is_empty() {
            params.push(format!("chain={chain}"));
        }
        if !params.is_empty() {
            url = format!("{url}?{}", params.join("&"));
        }
        self.get_raw(&url).await
    }

    pub async fn pair_stats(
        &self,
        chain_id: Option<u64>,
        sort_by: Option<&str>,
        limit: Option<u32>,
    ) -> Result<serde_json::Value> {
        let mut url = format!("{}{}", self.base_url, crate::routes::STATS_PAIRS);
        let mut params = vec![];
        if let Some(c) = chain_id {
            params.push(format!("chain_id={c}"));
        }
        if let Some(s) = sort_by {
            params.push(format!("sort_by={s}"));
        }
        if let Some(l) = limit {
            params.push(format!("limit={l}"));
        }
        if !params.is_empty() {
            url = format!("{url}?{}", params.join("&"));
        }
        self.get_raw(&url).await
    }

    pub async fn graph_summary(&self, chain_id: u64) -> Result<serde_json::Value> {
        self.get(&crate::routes::graph_summary(chain_id)).await
    }

    pub async fn graph_token(&self, chain_id: u64, token: &str) -> Result<serde_json::Value> {
        self.get(&crate::routes::graph_token(chain_id, token))
            .await
    }

    pub async fn token_pools(&self, chain_id: u64, token: &str) -> Result<serde_json::Value> {
        self.get(&crate::routes::token_pools(chain_id, token))
            .await
    }

    pub async fn diagnose_pair(
        &self,
        chain_id: u64,
        token_a: &str,
        token_b: &str,
    ) -> Result<serde_json::Value> {
        self.get(&crate::routes::diagnose_pair(chain_id, token_a, token_b))
            .await
    }

    pub async fn sync_audit(&self, chain_id: u64) -> Result<serde_json::Value> {
        self.get(&crate::routes::sync_audit(chain_id)).await
    }

    pub async fn sync_health(&self, chain_id: u64) -> Result<serde_json::Value> {
        self.get(&crate::routes::sync_health(chain_id)).await
    }

    pub async fn webhook_config(&self, chain_id: u64) -> Result<serde_json::Value> {
        self.get(&format!("/api/debug/webhook_config/{chain_id}"))
            .await
    }

    pub async fn pool_audit(&self, chain_id: u64) -> Result<serde_json::Value> {
        self.get(&crate::routes::pool_audit(chain_id)).await
    }

    pub async fn verify_quote(
        &self,
        chain_id: u64,
        body: &serde_json::Value,
    ) -> Result<serde_json::Value> {
        self.post(&crate::routes::verify_quote(chain_id), body)
            .await
    }

    pub async fn exotic_parity(
        &self,
        chain_id: u64,
        body: &serde_json::Value,
    ) -> Result<serde_json::Value> {
        self.post(&crate::routes::exotic_parity(chain_id), body)
            .await
    }

    pub async fn quote_breakdown(
        &self,
        chain_id: u64,
        body: &serde_json::Value,
    ) -> Result<serde_json::Value> {
        self.post(&format!("/api/debug/quote_breakdown/{chain_id}"), body)
            .await
    }

    pub async fn quote_debug(
        &self,
        chain_id: u64,
        token_in: &str,
        token_out: &str,
        amount_in: &str,
    ) -> Result<serde_json::Value> {
        self.get(&crate::routes::quote_debug(
            chain_id, token_in, token_out, amount_in,
        ))
        .await
    }

    pub async fn rpc_stats(&self) -> Result<serde_json::Value> {
        self.get(crate::routes::STATS_RPC).await
    }

    pub async fn warming_candidates(
        &self,
        chain_id: Option<u64>,
        limit: Option<u32>,
    ) -> Result<serde_json::Value> {
        let mut url = format!("{}{}", self.base_url, crate::routes::STATS_WARMING);
        let mut params = vec![];
        if let Some(c) = chain_id {
            params.push(format!("chain_id={c}"));
        }
        if let Some(l) = limit {
            params.push(format!("limit={l}"));
        }
        if !params.is_empty() {
            url = format!("{url}?{}", params.join("&"));
        }
        self.get_raw(&url).await
    }

    pub async fn set_log_level(&self, filter: &str) -> Result<serde_json::Value> {
        self.post(
            crate::routes::LOG_LEVEL,
            &serde_json::json!({ "filter": filter }),
        )
        .await
    }

    // --- Admin key management ---

    pub async fn issue_key(&self, body: &serde_json::Value) -> Result<serde_json::Value> {
        self.post(crate::routes::ADMIN_KEYS_ISSUE, body).await
    }

    pub async fn list_keys(&self) -> Result<serde_json::Value> {
        self.get(crate::routes::ADMIN_KEYS).await
    }

    pub async fn revoke_key(&self, key: &str) -> Result<()> {
        self.post_status(
            &crate::routes::admin_key_action(key, "revoke"),
            &serde_json::json!({}),
        )
        .await
    }

    pub async fn activate_key(&self, key: &str) -> Result<()> {
        self.post_status(
            &crate::routes::admin_key_action(key, "activate"),
            &serde_json::json!({}),
        )
        .await
    }

    pub async fn delete_key(&self, key: &str) -> Result<()> {
        self.post_status(
            &crate::routes::admin_key_action(key, "delete"),
            &serde_json::json!({}),
        )
        .await
    }

}