1use alloy::sol;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
6pub enum WalletType {
7 #[default]
9 Safe,
10 Proxy,
12}
13
14impl WalletType {
15 pub fn as_str(&self) -> &'static str {
16 match self {
17 WalletType::Safe => "SAFE",
18 WalletType::Proxy => "PROXY",
19 }
20 }
21}
22
23sol! {
24 #[derive(Debug, PartialEq, Eq)]
25 struct SafeTransaction {
26 address to;
27 uint8 operation;
28 bytes data;
29 uint256 value;
30 }
31
32 #[derive(Debug, PartialEq, Eq)]
33 struct SafeTransactionArgs {
34 address from_address;
35 uint256 nonce;
36 uint256 chain_id;
37 SafeTransaction[] transactions;
38 }
39
40 #[derive(Debug, PartialEq, Eq)]
41 struct SafeTx {
42 address to;
43 uint256 value;
44 bytes data;
45 uint8 operation;
46 uint256 safeTxGas;
47 uint256 baseGas;
48 uint256 gasPrice;
49 address gasToken;
50 address refundReceiver;
51 uint256 nonce;
52 }
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct TransactionRequest {
57 #[serde(rename = "type")]
58 pub type_: String,
59 pub from: String,
60 pub to: String,
61 #[serde(rename = "proxyWallet")]
62 pub proxy_wallet: String,
63 pub data: String,
64 pub signature: String,
65 }
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct RelayerTransactionResponse {
70 #[serde(rename = "transactionID")]
71 pub transaction_id: String,
72 #[serde(rename = "transactionHash")]
73 pub transaction_hash: Option<String>,
74}
75
76pub fn deserialize_nonce<'de, D>(deserializer: D) -> Result<u64, D::Error>
77where
78 D: serde::Deserializer<'de>,
79{
80 use serde::de;
81
82 struct NonceVisitor;
83
84 impl<'de> de::Visitor<'de> for NonceVisitor {
85 type Value = u64;
86
87 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
88 formatter.write_str("a u64 or string representing a u64")
89 }
90
91 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> {
92 Ok(v)
93 }
94
95 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
96 where
97 E: de::Error,
98 {
99 v.parse().map_err(de::Error::custom)
100 }
101 }
102
103 deserializer.deserialize_any(NonceVisitor)
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct NonceResponse {
108 #[serde(deserialize_with = "deserialize_nonce")]
109 pub nonce: u64,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct TransactionStatusResponse {
114 pub state: String,
115 #[serde(rename = "transactionHash")]
116 pub transaction_hash: Option<String>,
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
126 fn test_nonce_from_integer() {
127 let json = r#"{"nonce": 42}"#;
128 let resp: NonceResponse = serde_json::from_str(json).unwrap();
129 assert_eq!(resp.nonce, 42);
130 }
131
132 #[test]
133 fn test_nonce_from_string() {
134 let json = r#"{"nonce": "123"}"#;
135 let resp: NonceResponse = serde_json::from_str(json).unwrap();
136 assert_eq!(resp.nonce, 123);
137 }
138
139 #[test]
140 fn test_nonce_from_zero_integer() {
141 let json = r#"{"nonce": 0}"#;
142 let resp: NonceResponse = serde_json::from_str(json).unwrap();
143 assert_eq!(resp.nonce, 0);
144 }
145
146 #[test]
147 fn test_nonce_from_zero_string() {
148 let json = r#"{"nonce": "0"}"#;
149 let resp: NonceResponse = serde_json::from_str(json).unwrap();
150 assert_eq!(resp.nonce, 0);
151 }
152
153 #[test]
154 fn test_nonce_from_large_integer() {
155 let json = r#"{"nonce": 18446744073709551615}"#;
156 let resp: NonceResponse = serde_json::from_str(json).unwrap();
157 assert_eq!(resp.nonce, u64::MAX);
158 }
159
160 #[test]
161 fn test_nonce_from_large_string() {
162 let json = r#"{"nonce": "18446744073709551615"}"#;
163 let resp: NonceResponse = serde_json::from_str(json).unwrap();
164 assert_eq!(resp.nonce, u64::MAX);
165 }
166
167 #[test]
168 fn test_nonce_from_non_numeric_string_fails() {
169 let json = r#"{"nonce": "abc"}"#;
170 let result = serde_json::from_str::<NonceResponse>(json);
171 assert!(result.is_err());
172 }
173
174 #[test]
175 fn test_nonce_from_empty_string_fails() {
176 let json = r#"{"nonce": ""}"#;
177 let result = serde_json::from_str::<NonceResponse>(json);
178 assert!(result.is_err());
179 }
180
181 #[test]
182 fn test_nonce_from_null_fails() {
183 let json = r#"{"nonce": null}"#;
184 let result = serde_json::from_str::<NonceResponse>(json);
185 assert!(result.is_err());
186 }
187
188 #[test]
189 fn test_nonce_missing_field_fails() {
190 let json = r#"{}"#;
191 let result = serde_json::from_str::<NonceResponse>(json);
192 assert!(result.is_err());
193 }
194
195 #[test]
198 fn test_wallet_type_as_str() {
199 assert_eq!(WalletType::Safe.as_str(), "SAFE");
200 assert_eq!(WalletType::Proxy.as_str(), "PROXY");
201 }
202
203 #[test]
204 fn test_wallet_type_default_is_safe() {
205 assert_eq!(WalletType::default(), WalletType::Safe);
206 }
207
208 #[test]
211 fn test_transaction_request_serialization() {
212 let tx = TransactionRequest {
213 type_: "SAFE".to_string(),
214 from: "0xabc".to_string(),
215 to: "0xdef".to_string(),
216 proxy_wallet: "0x123".to_string(),
217 data: "0xdeadbeef".to_string(),
218 signature: "0xsig".to_string(),
219 };
220 let json = serde_json::to_value(&tx).unwrap();
221 assert_eq!(json["type"], "SAFE");
222 assert_eq!(json["from"], "0xabc");
223 assert_eq!(json["proxyWallet"], "0x123");
224 }
225
226 #[test]
227 fn test_transaction_request_deserialization() {
228 let json = r#"{
229 "type": "PROXY",
230 "from": "0xabc",
231 "to": "0xdef",
232 "proxyWallet": "0x123",
233 "data": "0xdeadbeef",
234 "signature": "0xsig"
235 }"#;
236 let tx: TransactionRequest = serde_json::from_str(json).unwrap();
237 assert_eq!(tx.type_, "PROXY");
238 assert_eq!(tx.proxy_wallet, "0x123");
239 }
240
241 #[test]
244 fn test_relayer_response_with_hash() {
245 let json = r#"{
246 "transactionID": "tx-123",
247 "transactionHash": "0xabcdef"
248 }"#;
249 let resp: RelayerTransactionResponse = serde_json::from_str(json).unwrap();
250 assert_eq!(resp.transaction_id, "tx-123");
251 assert_eq!(resp.transaction_hash.as_deref(), Some("0xabcdef"));
252 }
253
254 #[test]
255 fn test_relayer_response_without_hash() {
256 let json = r#"{
257 "transactionID": "tx-456",
258 "transactionHash": null
259 }"#;
260 let resp: RelayerTransactionResponse = serde_json::from_str(json).unwrap();
261 assert_eq!(resp.transaction_id, "tx-456");
262 assert!(resp.transaction_hash.is_none());
263 }
264
265 #[test]
266 fn test_relayer_response_missing_hash_field() {
267 let json = r#"{"transactionID": "tx-789"}"#;
268 let resp: RelayerTransactionResponse = serde_json::from_str(json).unwrap();
269 assert_eq!(resp.transaction_id, "tx-789");
270 assert!(resp.transaction_hash.is_none());
271 }
272
273 #[test]
276 fn test_transaction_status_response() {
277 let json = r#"{
278 "state": "CONFIRMED",
279 "transactionHash": "0xabc123"
280 }"#;
281 let resp: TransactionStatusResponse = serde_json::from_str(json).unwrap();
282 assert_eq!(resp.state, "CONFIRMED");
283 assert_eq!(resp.transaction_hash.as_deref(), Some("0xabc123"));
284 }
285
286 #[test]
287 fn test_transaction_status_pending() {
288 let json = r#"{
289 "state": "PENDING",
290 "transactionHash": null
291 }"#;
292 let resp: TransactionStatusResponse = serde_json::from_str(json).unwrap();
293 assert_eq!(resp.state, "PENDING");
294 assert!(resp.transaction_hash.is_none());
295 }
296}