Skip to main content

scope_web/api/
tx.rs

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