1use crate::models::WebhookNotification;
8use chrono::Utc;
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use strum::Display;
12use uuid::Uuid;
13
14#[derive(Debug, Serialize, Deserialize, Clone)]
16pub struct Job<T> {
17 pub message_id: String,
18 pub version: String,
19 pub timestamp: String,
20 pub job_type: JobType,
21 pub data: T,
22}
23
24impl<T> Job<T> {
25 pub fn new(job_type: JobType, data: T) -> Self {
26 Self {
27 message_id: Uuid::new_v4().to_string(),
28 version: "1.0".to_string(),
29 timestamp: Utc::now().timestamp().to_string(),
30 job_type,
31 data,
32 }
33 }
34}
35
36#[derive(Debug, Serialize, Deserialize, Display, Clone)]
38#[serde(tag = "type", rename_all = "snake_case")]
39pub enum JobType {
40 TransactionRequest,
41 TransactionSend,
42 TransactionStatusCheck,
43 NotificationSend,
44 SolanaTokenSwapRequest,
45}
46
47#[derive(Debug, Serialize, Deserialize, Clone)]
49pub struct TransactionRequest {
50 pub transaction_id: String,
51 pub relayer_id: String,
52 pub metadata: Option<HashMap<String, String>>,
53}
54
55impl TransactionRequest {
56 pub fn new(transaction_id: impl Into<String>, relayer_id: impl Into<String>) -> Self {
57 Self {
58 transaction_id: transaction_id.into(),
59 relayer_id: relayer_id.into(),
60 metadata: None,
61 }
62 }
63
64 pub fn with_metadata(mut self, metadata: HashMap<String, String>) -> Self {
65 self.metadata = Some(metadata);
66 self
67 }
68}
69
70#[derive(Debug, Serialize, Deserialize, Clone)]
71pub enum TransactionCommand {
72 Submit,
73 Cancel { reason: String },
74 Resubmit,
75 Resend,
76}
77
78#[derive(Debug, Serialize, Deserialize, Clone)]
80pub struct TransactionSend {
81 pub transaction_id: String,
82 pub relayer_id: String,
83 pub command: TransactionCommand,
84 pub metadata: Option<HashMap<String, String>>,
85}
86
87impl TransactionSend {
88 pub fn submit(transaction_id: impl Into<String>, relayer_id: impl Into<String>) -> Self {
89 Self {
90 transaction_id: transaction_id.into(),
91 relayer_id: relayer_id.into(),
92 command: TransactionCommand::Submit,
93 metadata: None,
94 }
95 }
96
97 pub fn cancel(
98 transaction_id: impl Into<String>,
99 relayer_id: impl Into<String>,
100 reason: impl Into<String>,
101 ) -> Self {
102 Self {
103 transaction_id: transaction_id.into(),
104 relayer_id: relayer_id.into(),
105 command: TransactionCommand::Cancel {
106 reason: reason.into(),
107 },
108 metadata: None,
109 }
110 }
111
112 pub fn resubmit(transaction_id: impl Into<String>, relayer_id: impl Into<String>) -> Self {
113 Self {
114 transaction_id: transaction_id.into(),
115 relayer_id: relayer_id.into(),
116 command: TransactionCommand::Resubmit,
117 metadata: None,
118 }
119 }
120
121 pub fn resend(transaction_id: impl Into<String>, relayer_id: impl Into<String>) -> Self {
122 Self {
123 transaction_id: transaction_id.into(),
124 relayer_id: relayer_id.into(),
125 command: TransactionCommand::Resend,
126 metadata: None,
127 }
128 }
129
130 pub fn with_metadata(mut self, metadata: HashMap<String, String>) -> Self {
131 self.metadata = Some(metadata);
132 self
133 }
134}
135
136#[derive(Debug, Serialize, Deserialize, Clone)]
138pub struct TransactionStatusCheck {
139 pub transaction_id: String,
140 pub relayer_id: String,
141 pub metadata: Option<HashMap<String, String>>,
142}
143
144impl TransactionStatusCheck {
145 pub fn new(transaction_id: impl Into<String>, relayer_id: impl Into<String>) -> Self {
146 Self {
147 transaction_id: transaction_id.into(),
148 relayer_id: relayer_id.into(),
149 metadata: None,
150 }
151 }
152
153 pub fn with_metadata(mut self, metadata: HashMap<String, String>) -> Self {
154 self.metadata = Some(metadata);
155 self
156 }
157}
158
159#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
160pub struct NotificationSend {
161 pub notification_id: String,
162 pub notification: WebhookNotification,
163}
164
165impl NotificationSend {
166 pub fn new(notification_id: String, notification: WebhookNotification) -> Self {
167 Self {
168 notification_id,
169 notification,
170 }
171 }
172}
173
174#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
175pub struct SolanaTokenSwapRequest {
176 pub relayer_id: String,
177}
178
179impl SolanaTokenSwapRequest {
180 pub fn new(relayer_id: String) -> Self {
181 Self { relayer_id }
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use std::collections::HashMap;
188 use std::str::FromStr;
189
190 use crate::models::{
191 EvmTransactionResponse, TransactionResponse, TransactionStatus, WebhookNotification,
192 WebhookPayload, U256,
193 };
194
195 use super::*;
196
197 #[test]
198 fn test_job_creation() {
199 let job_data = TransactionRequest::new("tx123", "relayer-1");
200 let job = Job::new(JobType::TransactionRequest, job_data.clone());
201
202 assert_eq!(job.job_type.to_string(), "TransactionRequest");
203 assert_eq!(job.version, "1.0");
204 assert_eq!(job.data.transaction_id, "tx123");
205 assert_eq!(job.data.relayer_id, "relayer-1");
206 assert!(job.data.metadata.is_none());
207 }
208
209 #[test]
210 fn test_transaction_request_with_metadata() {
211 let mut metadata = HashMap::new();
212 metadata.insert("chain_id".to_string(), "1".to_string());
213 metadata.insert("gas_price".to_string(), "20000000000".to_string());
214
215 let tx_request =
216 TransactionRequest::new("tx123", "relayer-1").with_metadata(metadata.clone());
217
218 assert_eq!(tx_request.transaction_id, "tx123");
219 assert_eq!(tx_request.relayer_id, "relayer-1");
220 assert!(tx_request.metadata.is_some());
221 assert_eq!(tx_request.metadata.unwrap(), metadata);
222 }
223
224 #[test]
225 fn test_transaction_send_methods() {
226 let tx_submit = TransactionSend::submit("tx123", "relayer-1");
228 assert_eq!(tx_submit.transaction_id, "tx123");
229 assert_eq!(tx_submit.relayer_id, "relayer-1");
230 matches!(tx_submit.command, TransactionCommand::Submit);
231
232 let tx_cancel = TransactionSend::cancel("tx123", "relayer-1", "user requested");
234 matches!(tx_cancel.command, TransactionCommand::Cancel { reason } if reason == "user requested");
235
236 let tx_resubmit = TransactionSend::resubmit("tx123", "relayer-1");
238 matches!(tx_resubmit.command, TransactionCommand::Resubmit);
239
240 let tx_resend = TransactionSend::resend("tx123", "relayer-1");
242 matches!(tx_resend.command, TransactionCommand::Resend);
243
244 let mut metadata = HashMap::new();
246 metadata.insert("nonce".to_string(), "5".to_string());
247
248 let tx_with_metadata =
249 TransactionSend::submit("tx123", "relayer-1").with_metadata(metadata.clone());
250
251 assert!(tx_with_metadata.metadata.is_some());
252 assert_eq!(tx_with_metadata.metadata.unwrap(), metadata);
253 }
254
255 #[test]
256 fn test_transaction_status_check() {
257 let tx_status = TransactionStatusCheck::new("tx123", "relayer-1");
258 assert_eq!(tx_status.transaction_id, "tx123");
259 assert_eq!(tx_status.relayer_id, "relayer-1");
260 assert!(tx_status.metadata.is_none());
261
262 let mut metadata = HashMap::new();
263 metadata.insert("retries".to_string(), "3".to_string());
264
265 let tx_status_with_metadata =
266 TransactionStatusCheck::new("tx123", "relayer-1").with_metadata(metadata.clone());
267
268 assert!(tx_status_with_metadata.metadata.is_some());
269 assert_eq!(tx_status_with_metadata.metadata.unwrap(), metadata);
270 }
271
272 #[test]
273 fn test_job_serialization() {
274 let tx_request = TransactionRequest::new("tx123", "relayer-1");
275 let job = Job::new(JobType::TransactionRequest, tx_request);
276
277 let serialized = serde_json::to_string(&job).unwrap();
278 let deserialized: Job<TransactionRequest> = serde_json::from_str(&serialized).unwrap();
279
280 assert_eq!(deserialized.job_type.to_string(), "TransactionRequest");
281 assert_eq!(deserialized.data.transaction_id, "tx123");
282 assert_eq!(deserialized.data.relayer_id, "relayer-1");
283 }
284
285 #[test]
286 fn test_notification_send_serialization() {
287 let payload =
288 WebhookPayload::Transaction(TransactionResponse::Evm(EvmTransactionResponse {
289 id: "tx123".to_string(),
290 hash: Some("0x123".to_string()),
291 status: TransactionStatus::Confirmed,
292 status_reason: None,
293 created_at: "2025-01-27T15:31:10.777083+00:00".to_string(),
294 sent_at: Some("2025-01-27T15:31:10.777083+00:00".to_string()),
295 confirmed_at: Some("2025-01-27T15:31:10.777083+00:00".to_string()),
296 gas_price: Some(1000000000),
297 gas_limit: 21000,
298 nonce: Some(1),
299 value: U256::from_str("1000000000000000000").unwrap(),
300 from: "0xabc".to_string(),
301 to: Some("0xdef".to_string()),
302 relayer_id: "relayer-1".to_string(),
303 }));
304
305 let notification = WebhookNotification::new("transaction".to_string(), payload);
306 let notification_send =
307 NotificationSend::new("notification-test".to_string(), notification);
308
309 let serialized = serde_json::to_string(¬ification_send).unwrap();
310 match serde_json::from_str::<NotificationSend>(&serialized) {
311 Ok(deserialized) => {
312 assert_eq!(notification_send, deserialized);
313 }
314 Err(e) => {
315 eprintln!("Failed to deserialize NotificationSend: {}", e);
316 panic!("Deserialization error: {}", e);
317 }
318 }
319 }
320}