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