1use crate::chains::ChainClientFactory;
4use crate::cli::address::{self, AddressArgs};
5use crate::web::AppState;
6use axum::Json;
7use axum::extract::State;
8use axum::http::StatusCode;
9use axum::response::IntoResponse;
10use serde::Deserialize;
11use std::sync::Arc;
12
13#[derive(Debug, Deserialize)]
15pub struct AddressRequest {
16 pub address: String,
18 #[serde(default = "default_chain")]
20 pub chain: String,
21 #[serde(default)]
23 pub include_txs: bool,
24 #[serde(default)]
26 pub include_tokens: bool,
27 #[serde(default = "default_limit")]
29 pub limit: u32,
30 #[serde(default)]
32 pub dossier: bool,
33}
34
35fn default_chain() -> String {
36 "ethereum".to_string()
37}
38
39fn default_limit() -> u32 {
40 100
41}
42
43pub async fn handle(
45 State(state): State<Arc<AppState>>,
46 Json(req): Json<AddressRequest>,
47) -> impl IntoResponse {
48 let args = AddressArgs {
49 address: req.address,
50 chain: req.chain,
51 format: None,
52 include_txs: req.include_txs,
53 include_tokens: req.include_tokens,
54 limit: req.limit,
55 report: None,
56 dossier: req.dossier,
57 };
58
59 let client: Box<dyn crate::chains::ChainClient> =
60 match state.factory.create_chain_client(&args.chain) {
61 Ok(c) => c,
62 Err(e) => {
63 return (
64 StatusCode::BAD_REQUEST,
65 Json(serde_json::json!({ "error": e.to_string() })),
66 )
67 .into_response();
68 }
69 };
70
71 match address::analyze_address(&args, client.as_ref()).await {
72 Ok(report) => Json(serde_json::json!(report)).into_response(),
73 Err(e) => (
74 StatusCode::INTERNAL_SERVER_ERROR,
75 Json(serde_json::json!({ "error": e.to_string() })),
76 )
77 .into_response(),
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn test_deserialize_full() {
87 let json = serde_json::json!({
88 "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f1b3c2",
89 "chain": "polygon",
90 "include_txs": true,
91 "include_tokens": true,
92 "limit": 50,
93 "dossier": true
94 });
95 let req: AddressRequest = serde_json::from_value(json).unwrap();
96 assert_eq!(req.address, "0x742d35Cc6634C0532925a3b844Bc9e7595f1b3c2");
97 assert_eq!(req.chain, "polygon");
98 assert!(req.include_txs);
99 assert!(req.include_tokens);
100 assert_eq!(req.limit, 50);
101 assert!(req.dossier);
102 }
103
104 #[test]
105 fn test_deserialize_minimal() {
106 let json = serde_json::json!({
107 "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f1b3c2"
108 });
109 let req: AddressRequest = serde_json::from_value(json).unwrap();
110 assert_eq!(req.address, "0x742d35Cc6634C0532925a3b844Bc9e7595f1b3c2");
111 assert_eq!(req.chain, "ethereum");
112 assert!(!req.include_txs);
113 assert!(!req.include_tokens);
114 assert_eq!(req.limit, 100);
115 assert!(!req.dossier);
116 }
117
118 #[test]
119 fn test_defaults() {
120 assert_eq!(default_chain(), "ethereum");
121 assert_eq!(default_limit(), 100);
122 }
123
124 #[test]
125 fn test_deserialize_with_options() {
126 let json = serde_json::json!({
127 "address": "0x1234567890123456789012345678901234567890",
128 "include_txs": true,
129 "dossier": true
130 });
131 let req: AddressRequest = serde_json::from_value(json).unwrap();
132 assert_eq!(req.address, "0x1234567890123456789012345678901234567890");
133 assert_eq!(req.chain, "ethereum");
134 assert!(req.include_txs);
135 assert!(!req.include_tokens);
136 assert_eq!(req.limit, 100);
137 assert!(req.dossier);
138 }
139
140 #[tokio::test]
141 async fn test_handle_address_direct() {
142 use crate::chains::DefaultClientFactory;
143 use crate::config::Config;
144 use crate::web::AppState;
145 use axum::extract::State;
146 use axum::response::IntoResponse;
147
148 let config = Config::default();
149 let factory = DefaultClientFactory {
150 chains_config: config.chains.clone(),
151 };
152 let state = std::sync::Arc::new(AppState { config, factory });
153 let req = AddressRequest {
154 address: "0x742d35Cc6634C0532925a3b844Bc9e7595f1b3c2".to_string(),
155 chain: "ethereum".to_string(),
156 include_txs: false,
157 include_tokens: true,
158 limit: 10,
159 dossier: false,
160 };
161 let response = handle(State(state), axum::Json(req)).await.into_response();
162 let status = response.status();
164 assert!(status.is_success() || status.is_client_error() || status.is_server_error());
165 }
166}