Skip to main content

scope/web/api/
tx.rs

1//! Transaction analysis API handler.
2
3use crate::cli::tx;
4use crate::web::AppState;
5use axum::Json;
6use axum::extract::State;
7use axum::http::StatusCode;
8use axum::response::IntoResponse;
9use serde::Deserialize;
10use std::sync::Arc;
11
12/// Request body for transaction analysis.
13#[derive(Debug, Deserialize)]
14pub struct TxRequest {
15    /// Transaction hash.
16    pub hash: String,
17    /// Target chain (default: "ethereum").
18    #[serde(default = "default_chain")]
19    pub chain: String,
20    /// Decode input data.
21    #[serde(default)]
22    pub decode: bool,
23    /// Include internal transaction trace.
24    #[serde(default)]
25    pub trace: bool,
26}
27
28fn default_chain() -> String {
29    "ethereum".to_string()
30}
31
32/// POST /api/tx — Analyze a transaction.
33pub async fn handle(
34    State(state): State<Arc<AppState>>,
35    Json(req): Json<TxRequest>,
36) -> impl IntoResponse {
37    match tx::fetch_transaction_report(&req.hash, &req.chain, req.decode, req.trace, &state.factory)
38        .await
39    {
40        Ok(report) => Json(serde_json::json!(report)).into_response(),
41        Err(e) => (
42            StatusCode::INTERNAL_SERVER_ERROR,
43            Json(serde_json::json!({ "error": e.to_string() })),
44        )
45            .into_response(),
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn test_deserialize_full() {
55        let json = serde_json::json!({
56            "hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
57            "chain": "polygon",
58            "decode": true,
59            "trace": true
60        });
61        let req: TxRequest = serde_json::from_value(json).unwrap();
62        assert_eq!(
63            req.hash,
64            "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
65        );
66        assert_eq!(req.chain, "polygon");
67        assert!(req.decode);
68        assert!(req.trace);
69    }
70
71    #[test]
72    fn test_deserialize_minimal() {
73        let json = serde_json::json!({
74            "hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
75        });
76        let req: TxRequest = serde_json::from_value(json).unwrap();
77        assert_eq!(
78            req.hash,
79            "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
80        );
81        assert_eq!(req.chain, "ethereum");
82        assert!(!req.decode);
83        assert!(!req.trace);
84    }
85
86    #[test]
87    fn test_defaults() {
88        assert_eq!(default_chain(), "ethereum");
89    }
90
91    #[test]
92    fn test_decode_trace_flags() {
93        let json_decode = serde_json::json!({
94            "hash": "0x123",
95            "decode": true,
96            "trace": false
97        });
98        let req_decode: TxRequest = serde_json::from_value(json_decode).unwrap();
99        assert!(req_decode.decode);
100        assert!(!req_decode.trace);
101
102        let json_trace = serde_json::json!({
103            "hash": "0x123",
104            "decode": false,
105            "trace": true
106        });
107        let req_trace: TxRequest = serde_json::from_value(json_trace).unwrap();
108        assert!(!req_trace.decode);
109        assert!(req_trace.trace);
110    }
111
112    #[tokio::test]
113    async fn test_handle_tx_direct() {
114        use crate::chains::DefaultClientFactory;
115        use crate::config::Config;
116        use crate::web::AppState;
117        use axum::extract::State;
118        use axum::response::IntoResponse;
119
120        let config = Config::default();
121        let factory = DefaultClientFactory {
122            chains_config: config.chains.clone(),
123        };
124        let state = std::sync::Arc::new(AppState { config, factory });
125        let req = TxRequest {
126            hash: "0xabc123def456789012345678901234567890123456789012345678901234abcd".to_string(),
127            chain: "ethereum".to_string(),
128            decode: false,
129            trace: false,
130        };
131        let response = handle(State(state), axum::Json(req)).await.into_response();
132        let status = response.status();
133        assert!(status.is_success() || status.is_client_error() || status.is_server_error());
134    }
135}