1use crate::{
13 domain::{
14 get_network_relayer, get_network_relayer_by_model, get_relayer_by_id,
15 get_relayer_transaction_by_model, get_transaction_by_id as get_tx_by_id, Relayer,
16 RelayerFactory, RelayerFactoryTrait, SignDataRequest, SignDataResponse,
17 SignTransactionRequest, SignTypedDataRequest, Transaction,
18 },
19 jobs::JobProducerTrait,
20 models::{
21 convert_to_internal_rpc_request, deserialize_policy_for_network_type, ApiError,
22 ApiResponse, CreateRelayerRequest, DefaultAppState, NetworkRepoModel,
23 NetworkTransactionRequest, NetworkType, NotificationRepoModel, PaginationMeta,
24 PaginationQuery, Relayer as RelayerDomainModel, RelayerRepoModel, RelayerRepoUpdater,
25 RelayerResponse, Signer as SignerDomainModel, SignerRepoModel, ThinDataAppState,
26 TransactionRepoModel, TransactionResponse, TransactionStatus, UpdateRelayerRequestRaw,
27 },
28 repositories::{
29 ApiKeyRepositoryTrait, NetworkRepository, PluginRepositoryTrait, RelayerRepository,
30 Repository, TransactionCounterTrait, TransactionRepository,
31 },
32 services::signer::{Signer, SignerFactory},
33};
34use actix_web::{web, HttpResponse};
35use eyre::Result;
36
37pub async fn list_relayers<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
48 query: PaginationQuery,
49 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
50) -> Result<HttpResponse, ApiError>
51where
52 J: JobProducerTrait + Send + Sync + 'static,
53 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
54 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
55 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
56 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
57 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
58 TCR: TransactionCounterTrait + Send + Sync + 'static,
59 PR: PluginRepositoryTrait + Send + Sync + 'static,
60 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
61{
62 let relayers = state.relayer_repository.list_paginated(query).await?;
63
64 let mapped_relayers: Vec<RelayerResponse> =
65 relayers.items.into_iter().map(|r| r.into()).collect();
66
67 Ok(HttpResponse::Ok().json(ApiResponse::paginated(
68 mapped_relayers,
69 PaginationMeta {
70 total_items: relayers.total,
71 current_page: relayers.page,
72 per_page: relayers.per_page,
73 },
74 )))
75}
76
77pub async fn get_relayer<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
88 relayer_id: String,
89 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
90) -> Result<HttpResponse, ApiError>
91where
92 J: JobProducerTrait + Send + Sync + 'static,
93 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
94 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
95 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
96 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
97 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
98 TCR: TransactionCounterTrait + Send + Sync + 'static,
99 PR: PluginRepositoryTrait + Send + Sync + 'static,
100 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
101{
102 let relayer = get_relayer_by_id(relayer_id, &state).await?;
103
104 let relayer_response: RelayerResponse = relayer.into();
105
106 Ok(HttpResponse::Ok().json(ApiResponse::success(relayer_response)))
107}
108
109pub async fn create_relayer<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
130 request: CreateRelayerRequest,
131 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
132) -> Result<HttpResponse, ApiError>
133where
134 J: JobProducerTrait + Send + Sync + 'static,
135 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
136 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
137 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
138 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
139 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
140 TCR: TransactionCounterTrait + Send + Sync + 'static,
141 PR: PluginRepositoryTrait + Send + Sync + 'static,
142 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
143{
144 let relayer = RelayerDomainModel::try_from(request)?;
146
147 let signer_model = state
149 .signer_repository
150 .get_by_id(relayer.signer_id.clone())
151 .await?;
152
153 let network = state
155 .network_repository
156 .get_by_name(relayer.network_type, &relayer.network)
157 .await?;
158
159 if network.is_none() {
160 return Err(ApiError::BadRequest(format!(
161 "Network '{}' not found for network type '{}'. Please ensure the network configuration exists.",
162 relayer.network,
163 relayer.network_type
164 )));
165 }
166
167 let relayers = state
169 .relayer_repository
170 .list_by_signer_id(&relayer.signer_id)
171 .await?;
172 if let Some(existing_relayer) = relayers.iter().find(|r| r.network == relayer.network) {
173 return Err(ApiError::BadRequest(format!(
174 "Cannot create relayer: signer '{}' is already in use by relayer '{}' on network '{}'. Each signer can only be connected to one relayer per network for security reasons. Please use a different signer or create the relayer on a different network.",
175 relayer.signer_id, existing_relayer.id, relayer.network
176 )));
177 }
178
179 if let Some(notification_id) = &relayer.notification_id {
181 let _notification = state
182 .notification_repository
183 .get_by_id(notification_id.clone())
184 .await?;
185 }
186
187 let mut relayer_model = RelayerRepoModel::from(relayer);
189
190 let signer_service = SignerFactory::create_signer(
192 &relayer_model.network_type,
193 &SignerDomainModel::from(signer_model.clone()),
194 )
195 .await
196 .map_err(|e| ApiError::InternalError(e.to_string()))?;
197 let address = signer_service
198 .address()
199 .await
200 .map_err(|e| ApiError::InternalError(e.to_string()))?;
201 relayer_model.address = address.to_string();
202
203 let created_relayer = state.relayer_repository.create(relayer_model).await?;
204
205 let relayer =
206 RelayerFactory::create_relayer(created_relayer.clone(), signer_model, &state).await?;
207
208 relayer.initialize_relayer().await?;
209
210 let response = RelayerResponse::from(created_relayer);
211 Ok(HttpResponse::Created().json(ApiResponse::success(response)))
212}
213
214pub async fn update_relayer<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
226 relayer_id: String,
227 patch: serde_json::Value,
228 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
229) -> Result<HttpResponse, ApiError>
230where
231 J: JobProducerTrait + Send + Sync + 'static,
232 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
233 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
234 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
235 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
236 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
237 TCR: TransactionCounterTrait + Send + Sync + 'static,
238 PR: PluginRepositoryTrait + Send + Sync + 'static,
239 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
240{
241 let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
242
243 let update_request: UpdateRelayerRequestRaw = serde_json::from_value(patch.clone())
245 .map_err(|e| ApiError::BadRequest(format!("Invalid update request: {e}")))?;
246
247 if let Some(policies) = update_request.policies {
248 deserialize_policy_for_network_type(&policies, relayer.network_type)
249 .map_err(|e| ApiError::BadRequest(format!("Invalid policy: {e}")))?;
250 }
251
252 if relayer.system_disabled {
253 return Err(ApiError::BadRequest("Relayer is disabled".to_string()));
254 }
255
256 if let Some(notification_id) = update_request.notification_id {
258 state
259 .notification_repository
260 .get_by_id(notification_id.to_string())
261 .await?;
262 }
263
264 let updated_domain = RelayerDomainModel::from(relayer.clone())
266 .apply_json_patch(&patch)
267 .map_err(ApiError::from)?;
268
269 let updated_repo_model =
271 RelayerRepoUpdater::from_existing(relayer).apply_domain_update(updated_domain);
272
273 let saved_relayer = state
274 .relayer_repository
275 .update(relayer_id.clone(), updated_repo_model)
276 .await?;
277
278 let relayer_response: RelayerResponse = saved_relayer.into();
279 Ok(HttpResponse::Ok().json(ApiResponse::success(relayer_response)))
280}
281
282pub async fn delete_relayer<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
298 relayer_id: String,
299 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
300) -> Result<HttpResponse, ApiError>
301where
302 J: JobProducerTrait + Send + Sync + 'static,
303 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
304 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
305 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
306 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
307 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
308 TCR: TransactionCounterTrait + Send + Sync + 'static,
309 PR: PluginRepositoryTrait + Send + Sync + 'static,
310 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
311{
312 let _relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
314
315 let transactions = state
317 .transaction_repository
318 .find_by_status(
319 &relayer_id,
320 &[
321 TransactionStatus::Pending,
322 TransactionStatus::Sent,
323 TransactionStatus::Submitted,
324 ],
325 )
326 .await?;
327
328 if !transactions.is_empty() {
329 return Err(ApiError::BadRequest(format!(
330 "Cannot delete relayer '{}' because it has {} transaction(s). Please wait for all transactions to complete or cancel them before deleting the relayer.",
331 relayer_id,
332 transactions.len()
333 )));
334 }
335
336 state.relayer_repository.delete_by_id(relayer_id).await?;
338
339 Ok(HttpResponse::Ok().json(ApiResponse::success("Relayer deleted successfully")))
340}
341
342pub async fn get_relayer_status<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
353 relayer_id: String,
354 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
355) -> Result<HttpResponse, ApiError>
356where
357 J: JobProducerTrait + Send + Sync + 'static,
358 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
359 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
360 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
361 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
362 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
363 TCR: TransactionCounterTrait + Send + Sync + 'static,
364 PR: PluginRepositoryTrait + Send + Sync + 'static,
365 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
366{
367 let relayer = get_network_relayer(relayer_id, &state).await?;
368
369 let status = relayer.get_status().await?;
370
371 Ok(HttpResponse::Ok().json(ApiResponse::success(status)))
372}
373
374pub async fn get_relayer_balance<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
385 relayer_id: String,
386 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
387) -> Result<HttpResponse, ApiError>
388where
389 J: JobProducerTrait + Send + Sync + 'static,
390 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
391 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
392 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
393 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
394 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
395 TCR: TransactionCounterTrait + Send + Sync + 'static,
396 PR: PluginRepositoryTrait + Send + Sync + 'static,
397 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
398{
399 let relayer = get_network_relayer(relayer_id, &state).await?;
400
401 let result = relayer.get_balance().await?;
402
403 Ok(HttpResponse::Ok().json(ApiResponse::success(result)))
404}
405
406pub async fn send_transaction(
418 relayer_id: String,
419 request: serde_json::Value,
420 state: web::ThinData<DefaultAppState>,
421) -> Result<HttpResponse, ApiError> {
422 let relayer_repo_model = get_relayer_by_id(relayer_id, &state).await?;
423 relayer_repo_model.validate_active_state()?;
424
425 let relayer = get_network_relayer(relayer_repo_model.id.clone(), &state).await?;
426
427 let tx_request: NetworkTransactionRequest =
428 NetworkTransactionRequest::from_json(&relayer_repo_model.network_type, request.clone())?;
429
430 tx_request.validate(&relayer_repo_model)?;
431
432 let transaction = relayer.process_transaction_request(tx_request).await?;
433
434 let transaction_response: TransactionResponse = transaction.into();
435
436 Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
437}
438
439pub async fn get_transaction_by_id<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
451 relayer_id: String,
452 transaction_id: String,
453 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
454) -> Result<HttpResponse, ApiError>
455where
456 J: JobProducerTrait + Send + Sync + 'static,
457 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
458 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
459 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
460 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
461 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
462 TCR: TransactionCounterTrait + Send + Sync + 'static,
463 PR: PluginRepositoryTrait + Send + Sync + 'static,
464 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
465{
466 if relayer_id.is_empty() || transaction_id.is_empty() {
467 return Ok(HttpResponse::Ok().json(ApiResponse::<()>::error(
468 "Invalid relayer or transaction ID".to_string(),
469 )));
470 }
471 get_relayer_by_id(relayer_id, &state).await?;
473
474 let transaction = get_tx_by_id(transaction_id, &state).await?;
475
476 let transaction_response: TransactionResponse = transaction.into();
477
478 Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
479}
480
481pub async fn get_transaction_by_nonce<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
493 relayer_id: String,
494 nonce: u64,
495 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
496) -> Result<HttpResponse, ApiError>
497where
498 J: JobProducerTrait + Send + Sync + 'static,
499 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
500 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
501 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
502 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
503 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
504 TCR: TransactionCounterTrait + Send + Sync + 'static,
505 PR: PluginRepositoryTrait + Send + Sync + 'static,
506 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
507{
508 let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
509
510 if relayer.network_type != NetworkType::Evm {
512 return Err(ApiError::NotSupported(
513 "Nonce lookup only supported for EVM networks".into(),
514 ));
515 }
516
517 let transaction = state
518 .transaction_repository
519 .find_by_nonce(&relayer_id, nonce)
520 .await?
521 .ok_or_else(|| ApiError::NotFound(format!("Transaction with nonce {nonce} not found")))?;
522
523 let transaction_response: TransactionResponse = transaction.into();
524
525 Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
526}
527
528pub async fn list_transactions<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
540 relayer_id: String,
541 query: PaginationQuery,
542 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
543) -> Result<HttpResponse, ApiError>
544where
545 J: JobProducerTrait + Send + Sync + 'static,
546 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
547 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
548 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
549 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
550 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
551 TCR: TransactionCounterTrait + Send + Sync + 'static,
552 PR: PluginRepositoryTrait + Send + Sync + 'static,
553 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
554{
555 get_relayer_by_id(relayer_id.clone(), &state).await?;
556
557 let transactions = state
558 .transaction_repository
559 .find_by_relayer_id(&relayer_id, query)
560 .await?;
561
562 let transaction_response_list: Vec<TransactionResponse> =
563 transactions.items.into_iter().map(|t| t.into()).collect();
564
565 Ok(HttpResponse::Ok().json(ApiResponse::paginated(
566 transaction_response_list,
567 PaginationMeta {
568 total_items: transactions.total,
569 current_page: transactions.page,
570 per_page: transactions.per_page,
571 },
572 )))
573}
574
575pub async fn delete_pending_transactions<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
586 relayer_id: String,
587 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
588) -> Result<HttpResponse, ApiError>
589where
590 J: JobProducerTrait + Send + Sync + 'static,
591 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
592 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
593 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
594 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
595 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
596 TCR: TransactionCounterTrait + Send + Sync + 'static,
597 PR: PluginRepositoryTrait + Send + Sync + 'static,
598 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
599{
600 let relayer = get_relayer_by_id(relayer_id, &state).await?;
601 relayer.validate_active_state()?;
602 let network_relayer = get_network_relayer_by_model(relayer.clone(), &state).await?;
603
604 let result = network_relayer.delete_pending_transactions().await?;
605
606 Ok(HttpResponse::Ok().json(ApiResponse::success(result)))
607}
608
609pub async fn cancel_transaction(
621 relayer_id: String,
622 transaction_id: String,
623 state: web::ThinData<DefaultAppState>,
624) -> Result<HttpResponse, ApiError> {
625 let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
626 relayer.validate_active_state()?;
627
628 let relayer_transaction = get_relayer_transaction_by_model(relayer.clone(), &state).await?;
629
630 let transaction_to_cancel = get_tx_by_id(transaction_id, &state).await?;
631
632 let canceled_transaction = relayer_transaction
633 .cancel_transaction(transaction_to_cancel)
634 .await?;
635
636 let transaction_response: TransactionResponse = canceled_transaction.into();
637
638 Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
639}
640
641pub async fn replace_transaction(
654 relayer_id: String,
655 transaction_id: String,
656 request: serde_json::Value,
657 state: web::ThinData<DefaultAppState>,
658) -> Result<HttpResponse, ApiError> {
659 let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
660 relayer.validate_active_state()?;
661
662 let new_tx_request: NetworkTransactionRequest =
663 NetworkTransactionRequest::from_json(&relayer.network_type, request.clone())?;
664 new_tx_request.validate(&relayer)?;
665
666 let transaction_to_replace = state
667 .transaction_repository
668 .get_by_id(transaction_id)
669 .await?;
670
671 let relayer_transaction = get_relayer_transaction_by_model(relayer.clone(), &state).await?;
672 let replaced_transaction = relayer_transaction
673 .replace_transaction(transaction_to_replace, new_tx_request)
674 .await?;
675
676 let transaction_response: TransactionResponse = replaced_transaction.into();
677
678 Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
679}
680
681pub async fn sign_data<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
693 relayer_id: String,
694 request: SignDataRequest,
695 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
696) -> Result<HttpResponse, ApiError>
697where
698 J: JobProducerTrait + Send + Sync + 'static,
699 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
700 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
701 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
702 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
703 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
704 TCR: TransactionCounterTrait + Send + Sync + 'static,
705 PR: PluginRepositoryTrait + Send + Sync + 'static,
706 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
707{
708 let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
709 relayer.validate_active_state()?;
710 let network_relayer = get_network_relayer_by_model(relayer, &state).await?;
711
712 let result = network_relayer.sign_data(request).await?;
713
714 if let SignDataResponse::Evm(sign) = result {
715 Ok(HttpResponse::Ok().json(ApiResponse::success(sign)))
716 } else {
717 Err(ApiError::NotSupported("Sign data not supported".into()))
718 }
719}
720
721pub async fn sign_typed_data<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
733 relayer_id: String,
734 request: SignTypedDataRequest,
735 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
736) -> Result<HttpResponse, ApiError>
737where
738 J: JobProducerTrait + Send + Sync + 'static,
739 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
740 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
741 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
742 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
743 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
744 TCR: TransactionCounterTrait + Send + Sync + 'static,
745 PR: PluginRepositoryTrait + Send + Sync + 'static,
746 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
747{
748 let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
749 relayer.validate_active_state()?;
750 let network_relayer = get_network_relayer_by_model(relayer, &state).await?;
751
752 let result = network_relayer.sign_typed_data(request).await?;
753
754 Ok(HttpResponse::Ok().json(ApiResponse::success(result)))
755}
756
757pub async fn relayer_rpc<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
769 relayer_id: String,
770 request: serde_json::Value,
771 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
772) -> Result<HttpResponse, ApiError>
773where
774 J: JobProducerTrait + Send + Sync + 'static,
775 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
776 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
777 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
778 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
779 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
780 TCR: TransactionCounterTrait + Send + Sync + 'static,
781 PR: PluginRepositoryTrait + Send + Sync + 'static,
782 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
783{
784 let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
785 relayer.validate_active_state()?;
786 let network_relayer = get_network_relayer_by_model(relayer.clone(), &state).await?;
787
788 let internal_request = convert_to_internal_rpc_request(request, &relayer.network_type)?;
789 let result = network_relayer.rpc(internal_request).await?;
790
791 Ok(HttpResponse::Ok().json(result))
792}
793
794pub async fn sign_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
806 relayer_id: String,
807 request: SignTransactionRequest,
808 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
809) -> Result<HttpResponse, ApiError>
810where
811 J: JobProducerTrait + Send + Sync + 'static,
812 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
813 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
814 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
815 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
816 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
817 TCR: TransactionCounterTrait + Send + Sync + 'static,
818 PR: PluginRepositoryTrait + Send + Sync + 'static,
819 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
820{
821 let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
822 relayer.validate_active_state()?;
823
824 let network_relayer = get_network_relayer_by_model(relayer, &state).await?;
826 let result = network_relayer.sign_transaction(&request).await?;
827
828 Ok(HttpResponse::Ok().json(ApiResponse::success(result)))
829}
830
831#[cfg(test)]
832mod tests {
833 use super::*;
834 use crate::{
835 domain::SignTransactionRequestStellar,
836 models::{
837 ApiResponse, CreateRelayerPolicyRequest, CreateRelayerRequest, RelayerEvmPolicy,
838 RelayerNetworkPolicyResponse, RelayerNetworkType, RelayerResponse, RelayerSolanaPolicy,
839 RelayerStellarPolicy, SolanaFeePaymentStrategy,
840 },
841 utils::mocks::mockutils::{
842 create_mock_app_state, create_mock_network, create_mock_notification,
843 create_mock_relayer, create_mock_signer, create_mock_transaction,
844 },
845 };
846 use actix_web::body::to_bytes;
847 use lazy_static::lazy_static;
848 use std::env;
849 use tokio::sync::Mutex;
850
851 lazy_static! {
852 static ref ENV_MUTEX: Mutex<()> = Mutex::new(());
853 }
854
855 fn setup_test_env() {
856 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D"); env::set_var("REDIS_URL", "redis://localhost:6379");
858 }
859
860 fn cleanup_test_env() {
861 env::remove_var("API_KEY");
862 env::remove_var("REDIS_URL");
863 }
864
865 fn create_test_relayer_create_request(
867 id: Option<String>,
868 name: &str,
869 network: &str,
870 signer_id: &str,
871 notification_id: Option<String>,
872 ) -> CreateRelayerRequest {
873 CreateRelayerRequest {
874 id,
875 name: name.to_string(),
876 network: network.to_string(),
877 network_type: RelayerNetworkType::Evm,
878 paused: false,
879 policies: None,
880 signer_id: signer_id.to_string(),
881 notification_id,
882 custom_rpc_urls: None,
883 }
884 }
885
886 fn create_mock_solana_network() -> crate::models::NetworkRepoModel {
888 use crate::config::{NetworkConfigCommon, SolanaNetworkConfig};
889 use crate::models::{NetworkConfigData, NetworkRepoModel, NetworkType};
890
891 NetworkRepoModel {
892 id: "test".to_string(),
893 name: "test".to_string(),
894 network_type: NetworkType::Solana,
895 config: NetworkConfigData::Solana(SolanaNetworkConfig {
896 common: NetworkConfigCommon {
897 network: "test".to_string(),
898 from: None,
899 rpc_urls: Some(vec!["http://localhost:8899".to_string()]),
900 explorer_urls: None,
901 average_blocktime_ms: Some(400),
902 is_testnet: Some(true),
903 tags: None,
904 },
905 }),
906 }
907 }
908
909 fn create_mock_stellar_network() -> crate::models::NetworkRepoModel {
911 use crate::config::{NetworkConfigCommon, StellarNetworkConfig};
912 use crate::models::{NetworkConfigData, NetworkRepoModel, NetworkType};
913
914 NetworkRepoModel {
915 id: "test".to_string(),
916 name: "test".to_string(),
917 network_type: NetworkType::Stellar,
918 config: NetworkConfigData::Stellar(StellarNetworkConfig {
919 common: NetworkConfigCommon {
920 network: "test".to_string(),
921 from: None,
922 rpc_urls: Some(vec!["https://horizon-testnet.stellar.org".to_string()]),
923 explorer_urls: None,
924 average_blocktime_ms: Some(5000),
925 is_testnet: Some(true),
926 tags: None,
927 },
928 passphrase: Some("Test Network ; September 2015".to_string()),
929 }),
930 }
931 }
932
933 #[actix_web::test]
936 async fn test_create_relayer_success() {
937 let _lock = ENV_MUTEX.lock().await;
938 setup_test_env();
939 let network = create_mock_network();
940 let signer = create_mock_signer();
941 let app_state = create_mock_app_state(
942 None,
943 None,
944 Some(vec![signer]),
945 Some(vec![network]),
946 None,
947 None,
948 )
949 .await;
950
951 let request = create_test_relayer_create_request(
952 Some("test-relayer".to_string()),
953 "Test Relayer",
954 "test", "test", None,
957 );
958
959 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
960
961 assert!(result.is_ok());
962 let response = result.unwrap();
963 assert_eq!(response.status(), 201);
964
965 let body = to_bytes(response.into_body()).await.unwrap();
966 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
967
968 assert!(api_response.success);
969 let data = api_response.data.unwrap();
970 assert_eq!(data.id, "test-relayer");
971 assert_eq!(data.name, "Test Relayer"); assert_eq!(data.network, "test");
973 cleanup_test_env();
974 }
975
976 #[actix_web::test]
977 async fn test_create_relayer_with_evm_policies() {
978 let _lock = ENV_MUTEX.lock().await;
979 setup_test_env();
980 let network = create_mock_network();
981 let signer = create_mock_signer();
982 let app_state = create_mock_app_state(
983 None,
984 None,
985 Some(vec![signer]),
986 Some(vec![network]),
987 None,
988 None,
989 )
990 .await;
991
992 let mut request = create_test_relayer_create_request(
993 Some("test-relayer-policies".to_string()),
994 "Test Relayer with Policies",
995 "test", "test", None,
998 );
999
1000 request.policies = Some(CreateRelayerPolicyRequest::Evm(RelayerEvmPolicy {
1002 gas_price_cap: Some(50000000000),
1003 min_balance: Some(1000000000000000000),
1004 eip1559_pricing: Some(true),
1005 private_transactions: Some(false),
1006 gas_limit_estimation: Some(true),
1007 whitelist_receivers: Some(vec![
1008 "0x1234567890123456789012345678901234567890".to_string()
1009 ]),
1010 }));
1011
1012 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1013
1014 assert!(result.is_ok());
1015 let response = result.unwrap();
1016 assert_eq!(response.status(), 201);
1017
1018 let body = to_bytes(response.into_body()).await.unwrap();
1019 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1020
1021 assert!(api_response.success);
1022 let data = api_response.data.unwrap();
1023 assert_eq!(data.id, "test-relayer-policies");
1024 assert_eq!(data.name, "Test Relayer with Policies");
1025 assert_eq!(data.network, "test");
1026
1027 assert!(data.policies.is_some());
1029 cleanup_test_env();
1030 }
1031
1032 #[actix_web::test]
1033 async fn test_create_relayer_with_partial_evm_policies() {
1034 let _lock = ENV_MUTEX.lock().await;
1035 setup_test_env();
1036 let network = create_mock_network();
1037 let signer = create_mock_signer();
1038 let app_state = create_mock_app_state(
1039 None,
1040 None,
1041 Some(vec![signer]),
1042 Some(vec![network]),
1043 None,
1044 None,
1045 )
1046 .await;
1047
1048 let mut request = create_test_relayer_create_request(
1049 Some("test-relayer-partial".to_string()),
1050 "Test Relayer with Partial Policies",
1051 "test",
1052 "test",
1053 None,
1054 );
1055
1056 request.policies = Some(CreateRelayerPolicyRequest::Evm(RelayerEvmPolicy {
1058 gas_price_cap: Some(30000000000),
1059 eip1559_pricing: Some(false),
1060 min_balance: None,
1061 private_transactions: None,
1062 gas_limit_estimation: None,
1063 whitelist_receivers: None,
1064 }));
1065
1066 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1067
1068 assert!(result.is_ok());
1069 let response = result.unwrap();
1070 assert_eq!(response.status(), 201);
1071
1072 let body = to_bytes(response.into_body()).await.unwrap();
1073 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1074
1075 assert!(api_response.success);
1076 let data = api_response.data.unwrap();
1077 assert_eq!(data.id, "test-relayer-partial");
1078
1079 assert!(data.policies.is_some());
1081 cleanup_test_env();
1082 }
1083
1084 #[actix_web::test]
1085 async fn test_create_relayer_with_solana_policies() {
1086 let _lock = ENV_MUTEX.lock().await;
1087 setup_test_env();
1088 let network = create_mock_solana_network();
1089 let signer = create_mock_signer();
1090 let app_state = create_mock_app_state(
1091 None,
1092 None,
1093 Some(vec![signer]),
1094 Some(vec![network]),
1095 None,
1096 None,
1097 )
1098 .await;
1099
1100 let mut request = create_test_relayer_create_request(
1101 Some("test-solana-relayer".to_string()),
1102 "Test Solana Relayer",
1103 "test",
1104 "test",
1105 None,
1106 );
1107
1108 request.network_type = RelayerNetworkType::Solana;
1110 request.policies = Some(CreateRelayerPolicyRequest::Solana(RelayerSolanaPolicy {
1111 fee_payment_strategy: Some(SolanaFeePaymentStrategy::Relayer),
1112 min_balance: Some(5000000),
1113 max_signatures: Some(10),
1114 max_tx_data_size: Some(1232),
1115 max_allowed_fee_lamports: Some(50000),
1116 allowed_programs: None, allowed_tokens: None,
1118 fee_margin_percentage: Some(10.0),
1119 allowed_accounts: None,
1120 disallowed_accounts: None,
1121 swap_config: None,
1122 }));
1123
1124 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1125
1126 assert!(result.is_ok());
1127 let response = result.unwrap();
1128 assert_eq!(response.status(), 201);
1129
1130 let body = to_bytes(response.into_body()).await.unwrap();
1131 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1132
1133 assert!(api_response.success);
1134 let data = api_response.data.unwrap();
1135 assert_eq!(data.id, "test-solana-relayer");
1136 assert_eq!(data.network_type, RelayerNetworkType::Solana);
1137 assert_eq!(data.name, "Test Solana Relayer");
1138
1139 assert!(data.policies.is_some());
1141 let policies = data.policies.unwrap();
1143 if let RelayerNetworkPolicyResponse::Solana(solana_policy) = policies {
1144 assert_eq!(
1145 solana_policy.fee_payment_strategy,
1146 Some(SolanaFeePaymentStrategy::Relayer)
1147 );
1148 assert_eq!(solana_policy.min_balance, 5000000);
1149 assert_eq!(solana_policy.max_signatures, Some(10));
1150 assert_eq!(solana_policy.max_tx_data_size, 1232);
1151 assert_eq!(solana_policy.max_allowed_fee_lamports, Some(50000));
1152 } else {
1153 panic!("Expected Solana policies");
1154 }
1155 cleanup_test_env();
1156 }
1157
1158 #[actix_web::test]
1159 async fn test_create_relayer_with_stellar_policies() {
1160 let _lock = ENV_MUTEX.lock().await;
1161 setup_test_env();
1162 let network = create_mock_stellar_network();
1163 let signer = create_mock_signer();
1164 let app_state = create_mock_app_state(
1165 None,
1166 None,
1167 Some(vec![signer]),
1168 Some(vec![network]),
1169 None,
1170 None,
1171 )
1172 .await;
1173
1174 let mut request = create_test_relayer_create_request(
1175 Some("test-stellar-relayer".to_string()),
1176 "Test Stellar Relayer",
1177 "test",
1178 "test",
1179 None,
1180 );
1181
1182 request.network_type = RelayerNetworkType::Stellar;
1184 request.policies = Some(CreateRelayerPolicyRequest::Stellar(RelayerStellarPolicy {
1185 min_balance: Some(10000000),
1186 max_fee: Some(100),
1187 timeout_seconds: Some(30),
1188 concurrent_transactions: None,
1189 }));
1190
1191 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1192
1193 assert!(result.is_ok());
1194 let response = result.unwrap();
1195 assert_eq!(response.status(), 201);
1196
1197 let body = to_bytes(response.into_body()).await.unwrap();
1198 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1199
1200 assert!(api_response.success);
1201 let data = api_response.data.unwrap();
1202 assert_eq!(data.id, "test-stellar-relayer");
1203 assert_eq!(data.network_type, RelayerNetworkType::Stellar);
1204
1205 assert!(data.policies.is_some());
1207 cleanup_test_env();
1208 }
1209
1210 #[actix_web::test]
1211 async fn test_create_relayer_with_policy_type_mismatch() {
1212 let _lock = ENV_MUTEX.lock().await;
1213 setup_test_env();
1214 let network = create_mock_network();
1215 let signer = create_mock_signer();
1216 let app_state = create_mock_app_state(
1217 None,
1218 None,
1219 Some(vec![signer]),
1220 Some(vec![network]),
1221 None,
1222 None,
1223 )
1224 .await;
1225
1226 let mut request = create_test_relayer_create_request(
1227 Some("test-mismatch-relayer".to_string()),
1228 "Test Mismatch Relayer",
1229 "test",
1230 "test",
1231 None,
1232 );
1233
1234 request.network_type = RelayerNetworkType::Evm;
1236 request.policies = Some(CreateRelayerPolicyRequest::Solana(
1237 RelayerSolanaPolicy::default(),
1238 ));
1239
1240 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1241
1242 assert!(result.is_err());
1243 if let Err(ApiError::BadRequest(msg)) = result {
1244 assert!(msg.contains("Policy type does not match relayer network type"));
1245 } else {
1246 panic!("Expected BadRequest error for policy type mismatch");
1247 }
1248 cleanup_test_env();
1249 }
1250
1251 #[actix_web::test]
1252 async fn test_create_relayer_with_notification() {
1253 let _lock = ENV_MUTEX.lock().await;
1254 setup_test_env();
1255 let network = create_mock_network();
1256 let signer = create_mock_signer();
1257 let notification = create_mock_notification("test-notification".to_string());
1258 let app_state = create_mock_app_state(
1259 None,
1260 None,
1261 Some(vec![signer]),
1262 Some(vec![network]),
1263 None,
1264 None,
1265 )
1266 .await;
1267
1268 app_state
1270 .notification_repository
1271 .create(notification)
1272 .await
1273 .unwrap();
1274
1275 let request = create_test_relayer_create_request(
1276 Some("test-relayer".to_string()),
1277 "Test Relayer",
1278 "test", "test", Some("test-notification".to_string()),
1281 );
1282
1283 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1284
1285 assert!(result.is_ok());
1286 let response = result.unwrap();
1287 assert_eq!(response.status(), 201);
1288 let body = to_bytes(response.into_body()).await.unwrap();
1289 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1290
1291 assert!(api_response.success);
1292 let data = api_response.data.unwrap();
1293 assert_eq!(data.notification_id, Some("test-notification".to_string()));
1294 cleanup_test_env();
1295 }
1296
1297 #[actix_web::test]
1298 async fn test_create_relayer_nonexistent_signer() {
1299 let network = create_mock_network();
1300 let app_state =
1301 create_mock_app_state(None, None, None, Some(vec![network]), None, None).await;
1302
1303 let request = create_test_relayer_create_request(
1304 Some("test-relayer".to_string()),
1305 "Test Relayer",
1306 "test", "nonexistent-signer",
1308 None,
1309 );
1310
1311 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1312
1313 assert!(result.is_err());
1314 if let Err(ApiError::NotFound(msg)) = result {
1315 assert!(msg.contains("Signer with ID nonexistent-signer not found"));
1316 } else {
1317 panic!("Expected NotFound error for nonexistent signer");
1318 }
1319 }
1320
1321 #[actix_web::test]
1322 async fn test_create_relayer_nonexistent_network() {
1323 let signer = create_mock_signer();
1324 let app_state =
1325 create_mock_app_state(None, None, Some(vec![signer]), None, None, None).await;
1326
1327 let request = create_test_relayer_create_request(
1328 Some("test-relayer".to_string()),
1329 "Test Relayer",
1330 "nonexistent-network",
1331 "test", None,
1333 );
1334
1335 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1336
1337 assert!(result.is_err());
1338 if let Err(ApiError::BadRequest(msg)) = result {
1339 assert!(msg.contains("Network 'nonexistent-network' not found"));
1340 assert!(msg.contains("network configuration exists"));
1341 } else {
1342 panic!("Expected BadRequest error for nonexistent network");
1343 }
1344 }
1345
1346 #[actix_web::test]
1347 async fn test_create_relayer_signer_already_in_use() {
1348 let network = create_mock_network();
1349 let signer = create_mock_signer();
1350 let mut existing_relayer = create_mock_relayer("existing-relayer".to_string(), false);
1351 existing_relayer.signer_id = "test".to_string(); existing_relayer.network = "test".to_string(); let app_state = create_mock_app_state(
1354 None,
1355 Some(vec![existing_relayer]),
1356 Some(vec![signer]),
1357 Some(vec![network]),
1358 None,
1359 None,
1360 )
1361 .await;
1362
1363 let request = create_test_relayer_create_request(
1364 Some("test-relayer".to_string()),
1365 "Test Relayer",
1366 "test", "test", None,
1369 );
1370
1371 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1372
1373 assert!(result.is_err());
1374 if let Err(ApiError::BadRequest(msg)) = result {
1375 assert!(msg.contains("signer 'test' is already in use"));
1376 assert!(msg.contains("relayer 'existing-relayer'"));
1377 assert!(msg.contains("network 'test'"));
1378 assert!(msg.contains("security reasons"));
1379 } else {
1380 panic!("Expected BadRequest error for signer already in use");
1381 }
1382 }
1383
1384 #[actix_web::test]
1385 async fn test_create_relayer_nonexistent_notification() {
1386 let network = create_mock_network();
1387 let signer = create_mock_signer();
1388 let app_state = create_mock_app_state(
1389 None,
1390 None,
1391 Some(vec![signer]),
1392 Some(vec![network]),
1393 None,
1394 None,
1395 )
1396 .await;
1397
1398 let request = create_test_relayer_create_request(
1399 Some("test-relayer".to_string()),
1400 "Test Relayer",
1401 "test", "test", Some("nonexistent-notification".to_string()),
1404 );
1405
1406 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1407
1408 assert!(result.is_err());
1409 if let Err(ApiError::NotFound(msg)) = result {
1410 assert!(msg.contains("Notification with ID 'nonexistent-notification' not found"));
1411 } else {
1412 panic!("Expected NotFound error for nonexistent notification");
1413 }
1414 }
1415
1416 #[actix_web::test]
1419 async fn test_list_relayers_success() {
1420 let relayer1 = create_mock_relayer("relayer-1".to_string(), false);
1421 let relayer2 = create_mock_relayer("relayer-2".to_string(), false);
1422 let app_state =
1423 create_mock_app_state(None, Some(vec![relayer1, relayer2]), None, None, None, None)
1424 .await;
1425
1426 let query = PaginationQuery {
1427 page: 1,
1428 per_page: 10,
1429 };
1430
1431 let result = list_relayers(query, actix_web::web::ThinData(app_state)).await;
1432
1433 assert!(result.is_ok());
1434 let response = result.unwrap();
1435 assert_eq!(response.status(), 200);
1436
1437 let body = to_bytes(response.into_body()).await.unwrap();
1438 let api_response: ApiResponse<Vec<RelayerResponse>> =
1439 serde_json::from_slice(&body).unwrap();
1440
1441 assert!(api_response.success);
1442 let data = api_response.data.unwrap();
1443 assert_eq!(data.len(), 2);
1444 }
1445
1446 #[actix_web::test]
1447 async fn test_list_relayers_empty() {
1448 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
1449
1450 let query = PaginationQuery {
1451 page: 1,
1452 per_page: 10,
1453 };
1454
1455 let result = list_relayers(query, actix_web::web::ThinData(app_state)).await;
1456
1457 assert!(result.is_ok());
1458 let response = result.unwrap();
1459 assert_eq!(response.status(), 200);
1460
1461 let body = to_bytes(response.into_body()).await.unwrap();
1462 let api_response: ApiResponse<Vec<RelayerResponse>> =
1463 serde_json::from_slice(&body).unwrap();
1464
1465 assert!(api_response.success);
1466 let data = api_response.data.unwrap();
1467 assert_eq!(data.len(), 0);
1468 }
1469
1470 #[actix_web::test]
1473 async fn test_get_relayer_success() {
1474 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1475 let app_state =
1476 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1477
1478 let result = get_relayer(
1479 "test-relayer".to_string(),
1480 actix_web::web::ThinData(app_state),
1481 )
1482 .await;
1483
1484 assert!(result.is_ok());
1485 let response = result.unwrap();
1486 assert_eq!(response.status(), 200);
1487
1488 let body = to_bytes(response.into_body()).await.unwrap();
1489 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1490
1491 assert!(api_response.success);
1492 let data = api_response.data.unwrap();
1493 assert_eq!(data.id, "test-relayer");
1494 assert_eq!(data.name, "Relayer test-relayer"); }
1496
1497 #[actix_web::test]
1498 async fn test_get_relayer_not_found() {
1499 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
1500
1501 let result = get_relayer(
1502 "nonexistent".to_string(),
1503 actix_web::web::ThinData(app_state),
1504 )
1505 .await;
1506
1507 assert!(result.is_err());
1508 if let Err(ApiError::NotFound(msg)) = result {
1509 assert!(msg.contains("Relayer with ID nonexistent not found"));
1510 } else {
1511 panic!("Expected NotFound error");
1512 }
1513 }
1514
1515 #[actix_web::test]
1518 async fn test_update_relayer_success() {
1519 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1520 let app_state =
1521 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1522
1523 let patch = serde_json::json!({
1524 "name": "Updated Relayer Name",
1525 "paused": true
1526 });
1527
1528 let result = update_relayer(
1529 "test-relayer".to_string(),
1530 patch,
1531 actix_web::web::ThinData(app_state),
1532 )
1533 .await;
1534
1535 assert!(result.is_ok());
1536 let response = result.unwrap();
1537 assert_eq!(response.status(), 200);
1538
1539 let body = to_bytes(response.into_body()).await.unwrap();
1540 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1541
1542 assert!(api_response.success);
1543 let data = api_response.data.unwrap();
1544 assert_eq!(data.name, "Updated Relayer Name");
1545 assert!(data.paused);
1546 }
1547
1548 #[actix_web::test]
1549 async fn test_update_relayer_system_disabled() {
1550 let mut relayer = create_mock_relayer("disabled-relayer".to_string(), false);
1551 relayer.system_disabled = true;
1552 let app_state =
1553 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1554
1555 let patch = serde_json::json!({
1556 "name": "Updated Name"
1557 });
1558
1559 let result = update_relayer(
1560 "disabled-relayer".to_string(),
1561 patch,
1562 actix_web::web::ThinData(app_state),
1563 )
1564 .await;
1565
1566 assert!(result.is_err());
1567 if let Err(ApiError::BadRequest(msg)) = result {
1568 assert!(msg.contains("Relayer is disabled"));
1569 } else {
1570 panic!("Expected BadRequest error for disabled relayer");
1571 }
1572 }
1573
1574 #[actix_web::test]
1575 async fn test_update_relayer_invalid_patch() {
1576 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1577 let app_state =
1578 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1579
1580 let patch = serde_json::json!({
1581 "invalid_field": "value"
1582 });
1583
1584 let result = update_relayer(
1585 "test-relayer".to_string(),
1586 patch,
1587 actix_web::web::ThinData(app_state),
1588 )
1589 .await;
1590
1591 assert!(result.is_err());
1592 if let Err(ApiError::BadRequest(msg)) = result {
1593 assert!(msg.contains("Invalid update request"));
1594 } else {
1595 panic!("Expected BadRequest error for invalid patch");
1596 }
1597 }
1598
1599 #[actix_web::test]
1600 async fn test_update_relayer_nonexistent() {
1601 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
1602
1603 let patch = serde_json::json!({
1604 "name": "Updated Name"
1605 });
1606
1607 let result = update_relayer(
1608 "nonexistent-relayer".to_string(),
1609 patch,
1610 actix_web::web::ThinData(app_state),
1611 )
1612 .await;
1613
1614 assert!(result.is_err());
1615 if let Err(ApiError::NotFound(msg)) = result {
1616 assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
1617 } else {
1618 panic!("Expected NotFound error for nonexistent relayer");
1619 }
1620 }
1621
1622 #[actix_web::test]
1623 async fn test_update_relayer_set_evm_policies() {
1624 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1625 let app_state =
1626 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1627
1628 let patch = serde_json::json!({
1629 "policies": {
1630 "gas_price_cap": 50000000000u64,
1631 "min_balance": 1000000000000000000u64,
1632 "eip1559_pricing": true,
1633 "private_transactions": false,
1634 "gas_limit_estimation": true,
1635 "whitelist_receivers": ["0x1234567890123456789012345678901234567890"]
1636 }
1637 });
1638
1639 let result = update_relayer(
1640 "test-relayer".to_string(),
1641 patch,
1642 actix_web::web::ThinData(app_state),
1643 )
1644 .await;
1645
1646 assert!(result.is_ok());
1647 let response = result.unwrap();
1648 assert_eq!(response.status(), 200);
1649
1650 let body = to_bytes(response.into_body()).await.unwrap();
1651 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1652
1653 assert!(api_response.success);
1654 let data = api_response.data.unwrap();
1655
1656 assert!(data.policies.is_some());
1659 }
1660
1661 #[actix_web::test]
1662 async fn test_update_relayer_partial_policy_update() {
1663 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1664 let app_state =
1665 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1666
1667 let patch1 = serde_json::json!({
1669 "policies": {
1670 "gas_price_cap": 30000000000u64,
1671 "min_balance": 500000000000000000u64,
1672 "eip1559_pricing": false
1673 }
1674 });
1675
1676 let result1 = update_relayer(
1677 "test-relayer".to_string(),
1678 patch1,
1679 actix_web::web::ThinData(app_state),
1680 )
1681 .await;
1682
1683 assert!(result1.is_ok());
1684
1685 let relayer2 = create_mock_relayer("test-relayer".to_string(), false);
1687 let app_state2 =
1688 create_mock_app_state(None, Some(vec![relayer2]), None, None, None, None).await;
1689
1690 let patch2 = serde_json::json!({
1692 "policies": {
1693 "gas_price_cap": 60000000000u64
1694 }
1695 });
1696
1697 let result2 = update_relayer(
1698 "test-relayer".to_string(),
1699 patch2,
1700 actix_web::web::ThinData(app_state2),
1701 )
1702 .await;
1703
1704 assert!(result2.is_ok());
1705 let response = result2.unwrap();
1706 assert_eq!(response.status(), 200);
1707
1708 let body = to_bytes(response.into_body()).await.unwrap();
1709 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1710
1711 assert!(api_response.success);
1712 let data = api_response.data.unwrap();
1713
1714 assert!(data.policies.is_some());
1716 }
1717
1718 #[actix_web::test]
1719 async fn test_update_relayer_unset_notification() {
1720 let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
1721 relayer.notification_id = Some("test-notification".to_string());
1722 let app_state =
1723 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1724
1725 let patch = serde_json::json!({
1726 "notification_id": null
1727 });
1728
1729 let result = update_relayer(
1730 "test-relayer".to_string(),
1731 patch,
1732 actix_web::web::ThinData(app_state),
1733 )
1734 .await;
1735
1736 assert!(result.is_ok());
1737 let response = result.unwrap();
1738 assert_eq!(response.status(), 200);
1739
1740 let body = to_bytes(response.into_body()).await.unwrap();
1741 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1742
1743 assert!(api_response.success);
1744 let data = api_response.data.unwrap();
1745 assert_eq!(data.notification_id, None);
1746 }
1747
1748 #[actix_web::test]
1749 async fn test_update_relayer_unset_custom_rpc_urls() {
1750 let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
1751 relayer.custom_rpc_urls = Some(vec![crate::models::RpcConfig {
1752 url: "https://custom-rpc.example.com".to_string(),
1753 weight: 50,
1754 }]);
1755 let app_state =
1756 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1757
1758 let patch = serde_json::json!({
1759 "custom_rpc_urls": null
1760 });
1761
1762 let result = update_relayer(
1763 "test-relayer".to_string(),
1764 patch,
1765 actix_web::web::ThinData(app_state),
1766 )
1767 .await;
1768
1769 assert!(result.is_ok());
1770 let response = result.unwrap();
1771 assert_eq!(response.status(), 200);
1772
1773 let body = to_bytes(response.into_body()).await.unwrap();
1774 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1775
1776 assert!(api_response.success);
1777 let data = api_response.data.unwrap();
1778 assert_eq!(data.custom_rpc_urls, None);
1779 }
1780
1781 #[actix_web::test]
1782 async fn test_update_relayer_set_custom_rpc_urls() {
1783 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1784 let app_state =
1785 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1786
1787 let patch = serde_json::json!({
1788 "custom_rpc_urls": [
1789 {
1790 "url": "https://rpc1.example.com",
1791 "weight": 80
1792 },
1793 {
1794 "url": "https://rpc2.example.com",
1795 "weight": 60
1796 }
1797 ]
1798 });
1799
1800 let result = update_relayer(
1801 "test-relayer".to_string(),
1802 patch,
1803 actix_web::web::ThinData(app_state),
1804 )
1805 .await;
1806
1807 assert!(result.is_ok());
1808 let response = result.unwrap();
1809 assert_eq!(response.status(), 200);
1810
1811 let body = to_bytes(response.into_body()).await.unwrap();
1812 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1813
1814 assert!(api_response.success);
1815 let data = api_response.data.unwrap();
1816
1817 assert!(data.custom_rpc_urls.is_some());
1818 let rpc_urls = data.custom_rpc_urls.unwrap();
1819 assert_eq!(rpc_urls.len(), 2);
1820 assert_eq!(rpc_urls[0].url, "https://rpc1.example.com");
1821 assert_eq!(rpc_urls[0].weight, 80);
1822 assert_eq!(rpc_urls[1].url, "https://rpc2.example.com");
1823 assert_eq!(rpc_urls[1].weight, 60);
1824 }
1825
1826 #[actix_web::test]
1827 async fn test_update_relayer_clear_policies() {
1828 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1829 let app_state =
1830 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1831
1832 let patch = serde_json::json!({
1833 "policies": null
1834 });
1835
1836 let result = update_relayer(
1837 "test-relayer".to_string(),
1838 patch,
1839 actix_web::web::ThinData(app_state),
1840 )
1841 .await;
1842
1843 assert!(result.is_ok());
1844 let response = result.unwrap();
1845 assert_eq!(response.status(), 200);
1846
1847 let body = to_bytes(response.into_body()).await.unwrap();
1848 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1849
1850 assert!(api_response.success);
1851 let data = api_response.data.unwrap();
1852 assert_eq!(data.policies, None);
1853 }
1854
1855 #[actix_web::test]
1856 async fn test_update_relayer_invalid_policy_structure() {
1857 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1858 let app_state =
1859 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1860
1861 let patch = serde_json::json!({
1862 "policies": {
1863 "invalid_field_name": "some_value"
1864 }
1865 });
1866
1867 let result = update_relayer(
1868 "test-relayer".to_string(),
1869 patch,
1870 actix_web::web::ThinData(app_state),
1871 )
1872 .await;
1873
1874 assert!(result.is_err());
1875 if let Err(ApiError::BadRequest(msg)) = result {
1876 assert!(msg.contains("Invalid policy"));
1877 } else {
1878 panic!("Expected BadRequest error for invalid policy structure");
1879 }
1880 }
1881
1882 #[actix_web::test]
1883 async fn test_update_relayer_invalid_evm_policy_values() {
1884 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1885 let app_state =
1886 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1887
1888 let patch = serde_json::json!({
1889 "policies": {
1890 "gas_price_cap": "invalid_number",
1891 "min_balance": -1
1892 }
1893 });
1894
1895 let result = update_relayer(
1896 "test-relayer".to_string(),
1897 patch,
1898 actix_web::web::ThinData(app_state),
1899 )
1900 .await;
1901
1902 assert!(result.is_err());
1903 if let Err(ApiError::BadRequest(msg)) = result {
1904 assert!(msg.contains("Invalid policy") || msg.contains("Invalid update request"));
1905 } else {
1906 panic!("Expected BadRequest error for invalid policy values");
1907 }
1908 }
1909
1910 #[actix_web::test]
1911 async fn test_update_relayer_multiple_fields_at_once() {
1912 let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
1913 relayer.notification_id = Some("old-notification".to_string());
1914 let app_state =
1915 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1916
1917 let patch = serde_json::json!({
1918 "name": "Multi-Update Relayer",
1919 "paused": true,
1920 "notification_id": null,
1921 "policies": {
1922 "gas_price_cap": 40000000000u64,
1923 "eip1559_pricing": true
1924 },
1925 "custom_rpc_urls": [
1926 {
1927 "url": "https://new-rpc.example.com",
1928 "weight": 90
1929 }
1930 ]
1931 });
1932
1933 let result = update_relayer(
1934 "test-relayer".to_string(),
1935 patch,
1936 actix_web::web::ThinData(app_state),
1937 )
1938 .await;
1939
1940 assert!(result.is_ok());
1941 let response = result.unwrap();
1942 assert_eq!(response.status(), 200);
1943
1944 let body = to_bytes(response.into_body()).await.unwrap();
1945 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1946
1947 assert!(api_response.success);
1948 let data = api_response.data.unwrap();
1949
1950 assert_eq!(data.name, "Multi-Update Relayer");
1952 assert!(data.paused);
1953 assert_eq!(data.notification_id, None);
1954
1955 assert!(data.policies.is_some());
1957 assert!(data.custom_rpc_urls.is_some());
1958 let rpc_urls = data.custom_rpc_urls.unwrap();
1959 assert_eq!(rpc_urls.len(), 1);
1960 assert_eq!(rpc_urls[0].url, "https://new-rpc.example.com");
1961 assert_eq!(rpc_urls[0].weight, 90);
1962 }
1963
1964 #[actix_web::test]
1965 async fn test_update_relayer_solana_policies() {
1966 use crate::models::{
1967 NetworkType, RelayerNetworkPolicy, RelayerSolanaPolicy, SolanaFeePaymentStrategy,
1968 };
1969
1970 let mut solana_relayer = create_mock_relayer("test-solana-relayer".to_string(), false);
1972 solana_relayer.network_type = NetworkType::Solana;
1973 solana_relayer.policies = RelayerNetworkPolicy::Solana(RelayerSolanaPolicy::default());
1974
1975 let app_state =
1976 create_mock_app_state(None, Some(vec![solana_relayer]), None, None, None, None).await;
1977
1978 let patch = serde_json::json!({
1979 "policies": {
1980 "fee_payment_strategy": "user",
1981 "min_balance": 2000000,
1982 "max_signatures": 5,
1983 "max_tx_data_size": 800,
1984 "max_allowed_fee_lamports": 25000,
1985 "fee_margin_percentage": 15.0
1986 }
1987 });
1988
1989 let result = update_relayer(
1990 "test-solana-relayer".to_string(),
1991 patch,
1992 actix_web::web::ThinData(app_state),
1993 )
1994 .await;
1995
1996 assert!(result.is_ok());
1997 let response = result.unwrap();
1998 assert_eq!(response.status(), 200);
1999
2000 let body = to_bytes(response.into_body()).await.unwrap();
2001 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
2002
2003 assert!(api_response.success);
2004 let data = api_response.data.unwrap();
2005
2006 assert!(data.policies.is_some());
2008 let policies = data.policies.unwrap();
2009 if let RelayerNetworkPolicyResponse::Solana(solana_policy) = policies {
2010 assert_eq!(
2011 solana_policy.fee_payment_strategy,
2012 Some(SolanaFeePaymentStrategy::User)
2013 );
2014 assert_eq!(solana_policy.min_balance, 2000000);
2015 assert_eq!(solana_policy.max_signatures, Some(5));
2016 assert_eq!(solana_policy.max_tx_data_size, 800);
2017 assert_eq!(solana_policy.max_allowed_fee_lamports, Some(25000));
2018 assert_eq!(solana_policy.fee_margin_percentage, Some(15.0));
2019 } else {
2020 panic!("Expected Solana policies in response");
2021 }
2022 }
2023
2024 #[actix_web::test]
2027 async fn test_delete_relayer_success() {
2028 let relayer = create_mock_relayer("test-relayer".to_string(), false);
2029 let app_state =
2030 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
2031
2032 let result = delete_relayer(
2033 "test-relayer".to_string(),
2034 actix_web::web::ThinData(app_state),
2035 )
2036 .await;
2037
2038 assert!(result.is_ok());
2039 let response = result.unwrap();
2040 assert_eq!(response.status(), 200);
2041
2042 let body = to_bytes(response.into_body()).await.unwrap();
2043 let api_response: ApiResponse<String> = serde_json::from_slice(&body).unwrap();
2044
2045 assert!(api_response.success);
2046 let data = api_response.data.unwrap();
2047 assert!(data.contains("Relayer deleted successfully"));
2048 }
2049
2050 #[actix_web::test]
2051 async fn test_delete_relayer_with_transactions() {
2052 let relayer = create_mock_relayer("relayer-with-tx".to_string(), false);
2053 let mut transaction = create_mock_transaction();
2054 transaction.id = "test-tx".to_string();
2055 transaction.relayer_id = "relayer-with-tx".to_string();
2056 let app_state = create_mock_app_state(
2057 None,
2058 Some(vec![relayer]),
2059 None,
2060 None,
2061 None,
2062 Some(vec![transaction]),
2063 )
2064 .await;
2065
2066 let result = delete_relayer(
2067 "relayer-with-tx".to_string(),
2068 actix_web::web::ThinData(app_state),
2069 )
2070 .await;
2071
2072 assert!(result.is_err());
2073 if let Err(ApiError::BadRequest(msg)) = result {
2074 assert!(msg.contains("Cannot delete relayer 'relayer-with-tx'"));
2075 assert!(msg.contains("has 1 transaction(s)"));
2076 assert!(msg.contains("wait for all transactions to complete"));
2077 } else {
2078 panic!("Expected BadRequest error for relayer with transactions");
2079 }
2080 }
2081
2082 #[actix_web::test]
2083 async fn test_delete_relayer_nonexistent() {
2084 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
2085
2086 let result = delete_relayer(
2087 "nonexistent-relayer".to_string(),
2088 actix_web::web::ThinData(app_state),
2089 )
2090 .await;
2091
2092 assert!(result.is_err());
2093 if let Err(ApiError::NotFound(msg)) = result {
2094 assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
2095 } else {
2096 panic!("Expected NotFound error for nonexistent relayer");
2097 }
2098 }
2099
2100 #[actix_web::test]
2101 async fn test_sign_transaction_success() {
2102 let _lock = ENV_MUTEX.lock().await;
2103 setup_test_env();
2104 let network = create_mock_stellar_network();
2105 let signer = create_mock_signer();
2106 let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
2107 relayer.network_type = NetworkType::Stellar;
2108 let app_state = create_mock_app_state(
2109 None,
2110 Some(vec![relayer]),
2111 Some(vec![signer]),
2112 Some(vec![network]),
2113 None,
2114 None,
2115 )
2116 .await;
2117
2118 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2119 unsigned_xdr: "test-unsigned-xdr".to_string(),
2120 });
2121
2122 let result = sign_transaction(
2123 "test-relayer".to_string(),
2124 request,
2125 actix_web::web::ThinData(app_state),
2126 )
2127 .await;
2128
2129 assert!(result.is_err());
2132 cleanup_test_env();
2133 }
2134
2135 #[actix_web::test]
2136 async fn test_sign_transaction_relayer_not_found() {
2137 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
2138
2139 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2140 unsigned_xdr: "test-unsigned-xdr".to_string(),
2141 });
2142
2143 let result = sign_transaction(
2144 "nonexistent-relayer".to_string(),
2145 request,
2146 actix_web::web::ThinData(app_state),
2147 )
2148 .await;
2149
2150 assert!(result.is_err());
2151 if let Err(ApiError::NotFound(msg)) = result {
2152 assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
2153 } else {
2154 panic!("Expected NotFound error for nonexistent relayer");
2155 }
2156 }
2157
2158 #[actix_web::test]
2159 async fn test_sign_transaction_relayer_disabled() {
2160 let mut relayer = create_mock_relayer("disabled-relayer".to_string(), false);
2161 relayer.paused = true;
2162 let app_state =
2163 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
2164
2165 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2166 unsigned_xdr: "test-unsigned-xdr".to_string(),
2167 });
2168
2169 let result = sign_transaction(
2170 "disabled-relayer".to_string(),
2171 request,
2172 actix_web::web::ThinData(app_state),
2173 )
2174 .await;
2175
2176 assert!(result.is_err());
2177 if let Err(ApiError::ForbiddenError(msg)) = result {
2178 assert!(msg.contains("Relayer paused"));
2179 } else {
2180 panic!("Expected ForbiddenError for paused relayer");
2181 }
2182 }
2183
2184 #[actix_web::test]
2185 async fn test_sign_transaction_system_disabled() {
2186 let mut relayer = create_mock_relayer("system-disabled-relayer".to_string(), false);
2187 relayer.system_disabled = true;
2188 let app_state =
2189 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
2190
2191 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2192 unsigned_xdr: "test-unsigned-xdr".to_string(),
2193 });
2194
2195 let result = sign_transaction(
2196 "system-disabled-relayer".to_string(),
2197 request,
2198 actix_web::web::ThinData(app_state),
2199 )
2200 .await;
2201
2202 assert!(result.is_err());
2203 if let Err(ApiError::ForbiddenError(msg)) = result {
2204 assert!(msg.contains("Relayer disabled"));
2205 } else {
2206 panic!("Expected ForbiddenError for system disabled relayer");
2207 }
2208 }
2209}