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 NetworkRepository, PluginRepositoryTrait, RelayerRepository, Repository,
30 TransactionCounterTrait, TransactionRepository,
31 },
32 services::{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>(
48 query: PaginationQuery,
49 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
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{
61 let relayers = state.relayer_repository.list_paginated(query).await?;
62
63 let mapped_relayers: Vec<RelayerResponse> =
64 relayers.items.into_iter().map(|r| r.into()).collect();
65
66 Ok(HttpResponse::Ok().json(ApiResponse::paginated(
67 mapped_relayers,
68 PaginationMeta {
69 total_items: relayers.total,
70 current_page: relayers.page,
71 per_page: relayers.per_page,
72 },
73 )))
74}
75
76pub async fn get_relayer<J, RR, TR, NR, NFR, SR, TCR, PR>(
87 relayer_id: String,
88 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
89) -> Result<HttpResponse, ApiError>
90where
91 J: JobProducerTrait + Send + Sync + 'static,
92 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
93 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
94 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
95 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
96 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
97 TCR: TransactionCounterTrait + Send + Sync + 'static,
98 PR: PluginRepositoryTrait + Send + Sync + 'static,
99{
100 let relayer = get_relayer_by_id(relayer_id, &state).await?;
101
102 let relayer_response: RelayerResponse = relayer.into();
103
104 Ok(HttpResponse::Ok().json(ApiResponse::success(relayer_response)))
105}
106
107pub async fn create_relayer<J, RR, TR, NR, NFR, SR, TCR, PR>(
128 request: CreateRelayerRequest,
129 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
130) -> Result<HttpResponse, ApiError>
131where
132 J: JobProducerTrait + Send + Sync + 'static,
133 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
134 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
135 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
136 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
137 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
138 TCR: TransactionCounterTrait + Send + Sync + 'static,
139 PR: PluginRepositoryTrait + Send + Sync + 'static,
140{
141 let relayer = RelayerDomainModel::try_from(request)?;
143
144 let signer_model = state
146 .signer_repository
147 .get_by_id(relayer.signer_id.clone())
148 .await?;
149
150 let network = state
152 .network_repository
153 .get_by_name(relayer.network_type, &relayer.network)
154 .await?;
155
156 if network.is_none() {
157 return Err(ApiError::BadRequest(format!(
158 "Network '{}' not found for network type '{}'. Please ensure the network configuration exists.",
159 relayer.network,
160 relayer.network_type
161 )));
162 }
163
164 let relayers = state
166 .relayer_repository
167 .list_by_signer_id(&relayer.signer_id)
168 .await?;
169 if let Some(existing_relayer) = relayers.iter().find(|r| r.network == relayer.network) {
170 return Err(ApiError::BadRequest(format!(
171 "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.",
172 relayer.signer_id, existing_relayer.id, relayer.network
173 )));
174 }
175
176 if let Some(notification_id) = &relayer.notification_id {
178 let _notification = state
179 .notification_repository
180 .get_by_id(notification_id.clone())
181 .await?;
182 }
183
184 let mut relayer_model = RelayerRepoModel::from(relayer);
186
187 let signer_service = SignerFactory::create_signer(
189 &relayer_model.network_type,
190 &SignerDomainModel::from(signer_model.clone()),
191 )
192 .await
193 .map_err(|e| ApiError::InternalError(e.to_string()))?;
194 let address = signer_service
195 .address()
196 .await
197 .map_err(|e| ApiError::InternalError(e.to_string()))?;
198 relayer_model.address = address.to_string();
199
200 let created_relayer = state.relayer_repository.create(relayer_model).await?;
201
202 let relayer =
203 RelayerFactory::create_relayer(created_relayer.clone(), signer_model, &state).await?;
204
205 relayer.initialize_relayer().await?;
206
207 let response = RelayerResponse::from(created_relayer);
208 Ok(HttpResponse::Created().json(ApiResponse::success(response)))
209}
210
211pub async fn update_relayer<J, RR, TR, NR, NFR, SR, TCR, PR>(
223 relayer_id: String,
224 patch: serde_json::Value,
225 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
226) -> Result<HttpResponse, ApiError>
227where
228 J: JobProducerTrait + Send + Sync + 'static,
229 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
230 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
231 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
232 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
233 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
234 TCR: TransactionCounterTrait + Send + Sync + 'static,
235 PR: PluginRepositoryTrait + Send + Sync + 'static,
236{
237 let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
238
239 let update_request: UpdateRelayerRequestRaw = serde_json::from_value(patch.clone())
241 .map_err(|e| ApiError::BadRequest(format!("Invalid update request: {}", e)))?;
242
243 if let Some(policies) = update_request.policies {
244 deserialize_policy_for_network_type(&policies, relayer.network_type)
245 .map_err(|e| ApiError::BadRequest(format!("Invalid policy: {}", e)))?;
246 }
247
248 if relayer.system_disabled {
249 return Err(ApiError::BadRequest("Relayer is disabled".to_string()));
250 }
251
252 if let Some(notification_id) = update_request.notification_id {
254 state
255 .notification_repository
256 .get_by_id(notification_id.to_string())
257 .await?;
258 }
259
260 let updated_domain = RelayerDomainModel::from(relayer.clone())
262 .apply_json_patch(&patch)
263 .map_err(ApiError::from)?;
264
265 let updated_repo_model =
267 RelayerRepoUpdater::from_existing(relayer).apply_domain_update(updated_domain);
268
269 let saved_relayer = state
270 .relayer_repository
271 .update(relayer_id.clone(), updated_repo_model)
272 .await?;
273
274 let relayer_response: RelayerResponse = saved_relayer.into();
275 Ok(HttpResponse::Ok().json(ApiResponse::success(relayer_response)))
276}
277
278pub async fn delete_relayer<J, RR, TR, NR, NFR, SR, TCR, PR>(
294 relayer_id: String,
295 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
296) -> Result<HttpResponse, ApiError>
297where
298 J: JobProducerTrait + Send + Sync + 'static,
299 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
300 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
301 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
302 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
303 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
304 TCR: TransactionCounterTrait + Send + Sync + 'static,
305 PR: PluginRepositoryTrait + Send + Sync + 'static,
306{
307 let _relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
309
310 let transactions = state
312 .transaction_repository
313 .find_by_status(
314 &relayer_id,
315 &[
316 TransactionStatus::Pending,
317 TransactionStatus::Sent,
318 TransactionStatus::Submitted,
319 ],
320 )
321 .await?;
322
323 if !transactions.is_empty() {
324 return Err(ApiError::BadRequest(format!(
325 "Cannot delete relayer '{}' because it has {} transaction(s). Please wait for all transactions to complete or cancel them before deleting the relayer.",
326 relayer_id,
327 transactions.len()
328 )));
329 }
330
331 state.relayer_repository.delete_by_id(relayer_id).await?;
333
334 Ok(HttpResponse::Ok().json(ApiResponse::success("Relayer deleted successfully")))
335}
336
337pub async fn get_relayer_status<J, RR, TR, NR, NFR, SR, TCR, PR>(
348 relayer_id: String,
349 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
350) -> Result<HttpResponse, ApiError>
351where
352 J: JobProducerTrait + Send + Sync + 'static,
353 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
354 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
355 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
356 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
357 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
358 TCR: TransactionCounterTrait + Send + Sync + 'static,
359 PR: PluginRepositoryTrait + Send + Sync + 'static,
360{
361 let relayer = get_network_relayer(relayer_id, &state).await?;
362
363 let status = relayer.get_status().await?;
364
365 Ok(HttpResponse::Ok().json(ApiResponse::success(status)))
366}
367
368pub async fn get_relayer_balance<J, RR, TR, NR, NFR, SR, TCR, PR>(
379 relayer_id: String,
380 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
381) -> Result<HttpResponse, ApiError>
382where
383 J: JobProducerTrait + Send + Sync + 'static,
384 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
385 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
386 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
387 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
388 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
389 TCR: TransactionCounterTrait + Send + Sync + 'static,
390 PR: PluginRepositoryTrait + Send + Sync + 'static,
391{
392 let relayer = get_network_relayer(relayer_id, &state).await?;
393
394 let result = relayer.get_balance().await?;
395
396 Ok(HttpResponse::Ok().json(ApiResponse::success(result)))
397}
398
399pub async fn send_transaction(
411 relayer_id: String,
412 request: serde_json::Value,
413 state: web::ThinData<DefaultAppState>,
414) -> Result<HttpResponse, ApiError> {
415 let relayer_repo_model = get_relayer_by_id(relayer_id, &state).await?;
416 relayer_repo_model.validate_active_state()?;
417
418 let relayer = get_network_relayer(relayer_repo_model.id.clone(), &state).await?;
419
420 let tx_request: NetworkTransactionRequest =
421 NetworkTransactionRequest::from_json(&relayer_repo_model.network_type, request.clone())?;
422
423 tx_request.validate(&relayer_repo_model)?;
424
425 let transaction = relayer.process_transaction_request(tx_request).await?;
426
427 let transaction_response: TransactionResponse = transaction.into();
428
429 Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
430}
431
432pub async fn get_transaction_by_id<J, RR, TR, NR, NFR, SR, TCR, PR>(
444 relayer_id: String,
445 transaction_id: String,
446 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
447) -> Result<HttpResponse, ApiError>
448where
449 J: JobProducerTrait + Send + Sync + 'static,
450 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
451 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
452 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
453 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
454 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
455 TCR: TransactionCounterTrait + Send + Sync + 'static,
456 PR: PluginRepositoryTrait + Send + Sync + 'static,
457{
458 if relayer_id.is_empty() || transaction_id.is_empty() {
459 return Ok(HttpResponse::Ok().json(ApiResponse::<()>::error(
460 "Invalid relayer or transaction ID".to_string(),
461 )));
462 }
463 get_relayer_by_id(relayer_id, &state).await?;
465
466 let transaction = get_tx_by_id(transaction_id, &state).await?;
467
468 let transaction_response: TransactionResponse = transaction.into();
469
470 Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
471}
472
473pub async fn get_transaction_by_nonce<J, RR, TR, NR, NFR, SR, TCR, PR>(
485 relayer_id: String,
486 nonce: u64,
487 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
488) -> Result<HttpResponse, ApiError>
489where
490 J: JobProducerTrait + Send + Sync + 'static,
491 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
492 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
493 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
494 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
495 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
496 TCR: TransactionCounterTrait + Send + Sync + 'static,
497 PR: PluginRepositoryTrait + Send + Sync + 'static,
498{
499 let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
500
501 if relayer.network_type != NetworkType::Evm {
503 return Err(ApiError::NotSupported(
504 "Nonce lookup only supported for EVM networks".into(),
505 ));
506 }
507
508 let transaction = state
509 .transaction_repository
510 .find_by_nonce(&relayer_id, nonce)
511 .await?
512 .ok_or_else(|| ApiError::NotFound(format!("Transaction with nonce {} not found", nonce)))?;
513
514 let transaction_response: TransactionResponse = transaction.into();
515
516 Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
517}
518
519pub async fn list_transactions<J, RR, TR, NR, NFR, SR, TCR, PR>(
531 relayer_id: String,
532 query: PaginationQuery,
533 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
534) -> Result<HttpResponse, ApiError>
535where
536 J: JobProducerTrait + Send + Sync + 'static,
537 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
538 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
539 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
540 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
541 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
542 TCR: TransactionCounterTrait + Send + Sync + 'static,
543 PR: PluginRepositoryTrait + Send + Sync + 'static,
544{
545 get_relayer_by_id(relayer_id.clone(), &state).await?;
546
547 let transactions = state
548 .transaction_repository
549 .find_by_relayer_id(&relayer_id, query)
550 .await?;
551
552 let transaction_response_list: Vec<TransactionResponse> =
553 transactions.items.into_iter().map(|t| t.into()).collect();
554
555 Ok(HttpResponse::Ok().json(ApiResponse::paginated(
556 transaction_response_list,
557 PaginationMeta {
558 total_items: transactions.total,
559 current_page: transactions.page,
560 per_page: transactions.per_page,
561 },
562 )))
563}
564
565pub async fn delete_pending_transactions<J, RR, TR, NR, NFR, SR, TCR, PR>(
576 relayer_id: String,
577 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
578) -> Result<HttpResponse, ApiError>
579where
580 J: JobProducerTrait + Send + Sync + 'static,
581 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
582 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
583 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
584 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
585 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
586 TCR: TransactionCounterTrait + Send + Sync + 'static,
587 PR: PluginRepositoryTrait + Send + Sync + 'static,
588{
589 let relayer = get_relayer_by_id(relayer_id, &state).await?;
590 relayer.validate_active_state()?;
591 let network_relayer = get_network_relayer_by_model(relayer.clone(), &state).await?;
592
593 let result = network_relayer.delete_pending_transactions().await?;
594
595 Ok(HttpResponse::Ok().json(ApiResponse::success(result)))
596}
597
598pub async fn cancel_transaction(
610 relayer_id: String,
611 transaction_id: String,
612 state: web::ThinData<DefaultAppState>,
613) -> Result<HttpResponse, ApiError> {
614 let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
615 relayer.validate_active_state()?;
616
617 let relayer_transaction = get_relayer_transaction_by_model(relayer.clone(), &state).await?;
618
619 let transaction_to_cancel = get_tx_by_id(transaction_id, &state).await?;
620
621 let canceled_transaction = relayer_transaction
622 .cancel_transaction(transaction_to_cancel)
623 .await?;
624
625 let transaction_response: TransactionResponse = canceled_transaction.into();
626
627 Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
628}
629
630pub async fn replace_transaction(
643 relayer_id: String,
644 transaction_id: String,
645 request: serde_json::Value,
646 state: web::ThinData<DefaultAppState>,
647) -> Result<HttpResponse, ApiError> {
648 let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
649 relayer.validate_active_state()?;
650
651 let new_tx_request: NetworkTransactionRequest =
652 NetworkTransactionRequest::from_json(&relayer.network_type, request.clone())?;
653 new_tx_request.validate(&relayer)?;
654
655 let transaction_to_replace = state
656 .transaction_repository
657 .get_by_id(transaction_id)
658 .await?;
659
660 let relayer_transaction = get_relayer_transaction_by_model(relayer.clone(), &state).await?;
661 let replaced_transaction = relayer_transaction
662 .replace_transaction(transaction_to_replace, new_tx_request)
663 .await?;
664
665 let transaction_response: TransactionResponse = replaced_transaction.into();
666
667 Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
668}
669
670pub async fn sign_data<J, RR, TR, NR, NFR, SR, TCR, PR>(
682 relayer_id: String,
683 request: SignDataRequest,
684 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
685) -> Result<HttpResponse, ApiError>
686where
687 J: JobProducerTrait + Send + Sync + 'static,
688 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
689 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
690 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
691 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
692 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
693 TCR: TransactionCounterTrait + Send + Sync + 'static,
694 PR: PluginRepositoryTrait + Send + Sync + 'static,
695{
696 let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
697 relayer.validate_active_state()?;
698 let network_relayer = get_network_relayer_by_model(relayer, &state).await?;
699
700 let result = network_relayer.sign_data(request).await?;
701
702 if let SignDataResponse::Evm(sign) = result {
703 Ok(HttpResponse::Ok().json(ApiResponse::success(sign)))
704 } else {
705 Err(ApiError::NotSupported("Sign data not supported".into()))
706 }
707}
708
709pub async fn sign_typed_data<J, RR, TR, NR, NFR, SR, TCR, PR>(
721 relayer_id: String,
722 request: SignTypedDataRequest,
723 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
724) -> Result<HttpResponse, ApiError>
725where
726 J: JobProducerTrait + Send + Sync + 'static,
727 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
728 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
729 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
730 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
731 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
732 TCR: TransactionCounterTrait + Send + Sync + 'static,
733 PR: PluginRepositoryTrait + Send + Sync + 'static,
734{
735 let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
736 relayer.validate_active_state()?;
737 let network_relayer = get_network_relayer_by_model(relayer, &state).await?;
738
739 let result = network_relayer.sign_typed_data(request).await?;
740
741 Ok(HttpResponse::Ok().json(ApiResponse::success(result)))
742}
743
744pub async fn relayer_rpc<J, RR, TR, NR, NFR, SR, TCR, PR>(
756 relayer_id: String,
757 request: serde_json::Value,
758 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
759) -> Result<HttpResponse, ApiError>
760where
761 J: JobProducerTrait + Send + Sync + 'static,
762 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
763 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
764 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
765 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
766 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
767 TCR: TransactionCounterTrait + Send + Sync + 'static,
768 PR: PluginRepositoryTrait + Send + Sync + 'static,
769{
770 let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
771 relayer.validate_active_state()?;
772 let network_relayer = get_network_relayer_by_model(relayer.clone(), &state).await?;
773
774 let internal_request = convert_to_internal_rpc_request(request, &relayer.network_type)?;
775 let result = network_relayer.rpc(internal_request).await?;
776
777 Ok(HttpResponse::Ok().json(result))
778}
779
780pub async fn sign_transaction<J, RR, TR, NR, NFR, SR, TCR, PR>(
792 relayer_id: String,
793 request: SignTransactionRequest,
794 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
795) -> Result<HttpResponse, ApiError>
796where
797 J: JobProducerTrait + Send + Sync + 'static,
798 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
799 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
800 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
801 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
802 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
803 TCR: TransactionCounterTrait + Send + Sync + 'static,
804 PR: PluginRepositoryTrait + Send + Sync + 'static,
805{
806 let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
807 relayer.validate_active_state()?;
808
809 let network_relayer = get_network_relayer_by_model(relayer, &state).await?;
811 let result = network_relayer.sign_transaction(&request).await?;
812
813 Ok(HttpResponse::Ok().json(ApiResponse::success(result)))
814}
815
816#[cfg(test)]
817mod tests {
818 use super::*;
819 use crate::{
820 domain::SignTransactionRequestStellar,
821 models::{
822 ApiResponse, CreateRelayerPolicyRequest, CreateRelayerRequest, RelayerEvmPolicy,
823 RelayerNetworkPolicyResponse, RelayerNetworkType, RelayerResponse, RelayerSolanaPolicy,
824 RelayerStellarPolicy, SolanaFeePaymentStrategy,
825 },
826 utils::mocks::mockutils::{
827 create_mock_app_state, create_mock_network, create_mock_notification,
828 create_mock_relayer, create_mock_signer, create_mock_transaction,
829 },
830 };
831 use actix_web::body::to_bytes;
832 use lazy_static::lazy_static;
833 use std::env;
834 use tokio::sync::Mutex;
835
836 lazy_static! {
837 static ref ENV_MUTEX: Mutex<()> = Mutex::new(());
838 }
839
840 fn setup_test_env() {
841 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D"); env::set_var("REDIS_URL", "redis://localhost:6379");
843 }
844
845 fn cleanup_test_env() {
846 env::remove_var("API_KEY");
847 env::remove_var("REDIS_URL");
848 }
849
850 fn create_test_relayer_create_request(
852 id: Option<String>,
853 name: &str,
854 network: &str,
855 signer_id: &str,
856 notification_id: Option<String>,
857 ) -> CreateRelayerRequest {
858 CreateRelayerRequest {
859 id,
860 name: name.to_string(),
861 network: network.to_string(),
862 network_type: RelayerNetworkType::Evm,
863 paused: false,
864 policies: None,
865 signer_id: signer_id.to_string(),
866 notification_id,
867 custom_rpc_urls: None,
868 }
869 }
870
871 fn create_mock_solana_network() -> crate::models::NetworkRepoModel {
873 use crate::config::{NetworkConfigCommon, SolanaNetworkConfig};
874 use crate::models::{NetworkConfigData, NetworkRepoModel, NetworkType};
875
876 NetworkRepoModel {
877 id: "test".to_string(),
878 name: "test".to_string(),
879 network_type: NetworkType::Solana,
880 config: NetworkConfigData::Solana(SolanaNetworkConfig {
881 common: NetworkConfigCommon {
882 network: "test".to_string(),
883 from: None,
884 rpc_urls: Some(vec!["http://localhost:8899".to_string()]),
885 explorer_urls: None,
886 average_blocktime_ms: Some(400),
887 is_testnet: Some(true),
888 tags: None,
889 },
890 }),
891 }
892 }
893
894 fn create_mock_stellar_network() -> crate::models::NetworkRepoModel {
896 use crate::config::{NetworkConfigCommon, StellarNetworkConfig};
897 use crate::models::{NetworkConfigData, NetworkRepoModel, NetworkType};
898
899 NetworkRepoModel {
900 id: "test".to_string(),
901 name: "test".to_string(),
902 network_type: NetworkType::Stellar,
903 config: NetworkConfigData::Stellar(StellarNetworkConfig {
904 common: NetworkConfigCommon {
905 network: "test".to_string(),
906 from: None,
907 rpc_urls: Some(vec!["https://horizon-testnet.stellar.org".to_string()]),
908 explorer_urls: None,
909 average_blocktime_ms: Some(5000),
910 is_testnet: Some(true),
911 tags: None,
912 },
913 passphrase: Some("Test Network ; September 2015".to_string()),
914 }),
915 }
916 }
917
918 #[actix_web::test]
921 async fn test_create_relayer_success() {
922 let _lock = ENV_MUTEX.lock().await;
923 setup_test_env();
924 let network = create_mock_network();
925 let signer = create_mock_signer();
926 let app_state =
927 create_mock_app_state(None, Some(vec![signer]), Some(vec![network]), None, None).await;
928
929 let request = create_test_relayer_create_request(
930 Some("test-relayer".to_string()),
931 "Test Relayer",
932 "test", "test", None,
935 );
936
937 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
938
939 assert!(result.is_ok());
940 let response = result.unwrap();
941 assert_eq!(response.status(), 201);
942
943 let body = to_bytes(response.into_body()).await.unwrap();
944 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
945
946 assert!(api_response.success);
947 let data = api_response.data.unwrap();
948 assert_eq!(data.id, "test-relayer");
949 assert_eq!(data.name, "Test Relayer"); assert_eq!(data.network, "test");
951 cleanup_test_env();
952 }
953
954 #[actix_web::test]
955 async fn test_create_relayer_with_evm_policies() {
956 let _lock = ENV_MUTEX.lock().await;
957 setup_test_env();
958 let network = create_mock_network();
959 let signer = create_mock_signer();
960 let app_state =
961 create_mock_app_state(None, Some(vec![signer]), Some(vec![network]), None, None).await;
962
963 let mut request = create_test_relayer_create_request(
964 Some("test-relayer-policies".to_string()),
965 "Test Relayer with Policies",
966 "test", "test", None,
969 );
970
971 request.policies = Some(CreateRelayerPolicyRequest::Evm(RelayerEvmPolicy {
973 gas_price_cap: Some(50000000000),
974 min_balance: Some(1000000000000000000),
975 eip1559_pricing: Some(true),
976 private_transactions: Some(false),
977 gas_limit_estimation: Some(true),
978 whitelist_receivers: Some(vec![
979 "0x1234567890123456789012345678901234567890".to_string()
980 ]),
981 }));
982
983 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
984
985 assert!(result.is_ok());
986 let response = result.unwrap();
987 assert_eq!(response.status(), 201);
988
989 let body = to_bytes(response.into_body()).await.unwrap();
990 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
991
992 assert!(api_response.success);
993 let data = api_response.data.unwrap();
994 assert_eq!(data.id, "test-relayer-policies");
995 assert_eq!(data.name, "Test Relayer with Policies");
996 assert_eq!(data.network, "test");
997
998 assert!(data.policies.is_some());
1000 cleanup_test_env();
1001 }
1002
1003 #[actix_web::test]
1004 async fn test_create_relayer_with_partial_evm_policies() {
1005 let _lock = ENV_MUTEX.lock().await;
1006 setup_test_env();
1007 let network = create_mock_network();
1008 let signer = create_mock_signer();
1009 let app_state =
1010 create_mock_app_state(None, Some(vec![signer]), Some(vec![network]), None, None).await;
1011
1012 let mut request = create_test_relayer_create_request(
1013 Some("test-relayer-partial".to_string()),
1014 "Test Relayer with Partial Policies",
1015 "test",
1016 "test",
1017 None,
1018 );
1019
1020 request.policies = Some(CreateRelayerPolicyRequest::Evm(RelayerEvmPolicy {
1022 gas_price_cap: Some(30000000000),
1023 eip1559_pricing: Some(false),
1024 min_balance: None,
1025 private_transactions: None,
1026 gas_limit_estimation: None,
1027 whitelist_receivers: None,
1028 }));
1029
1030 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1031
1032 assert!(result.is_ok());
1033 let response = result.unwrap();
1034 assert_eq!(response.status(), 201);
1035
1036 let body = to_bytes(response.into_body()).await.unwrap();
1037 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1038
1039 assert!(api_response.success);
1040 let data = api_response.data.unwrap();
1041 assert_eq!(data.id, "test-relayer-partial");
1042
1043 assert!(data.policies.is_some());
1045 cleanup_test_env();
1046 }
1047
1048 #[actix_web::test]
1049 async fn test_create_relayer_with_solana_policies() {
1050 let _lock = ENV_MUTEX.lock().await;
1051 setup_test_env();
1052 let network = create_mock_solana_network();
1053 let signer = create_mock_signer();
1054 let app_state =
1055 create_mock_app_state(None, Some(vec![signer]), Some(vec![network]), None, None).await;
1056
1057 let mut request = create_test_relayer_create_request(
1058 Some("test-solana-relayer".to_string()),
1059 "Test Solana Relayer",
1060 "test",
1061 "test",
1062 None,
1063 );
1064
1065 request.network_type = RelayerNetworkType::Solana;
1067 request.policies = Some(CreateRelayerPolicyRequest::Solana(RelayerSolanaPolicy {
1068 fee_payment_strategy: Some(SolanaFeePaymentStrategy::Relayer),
1069 min_balance: Some(5000000),
1070 max_signatures: Some(10),
1071 max_tx_data_size: Some(1232),
1072 max_allowed_fee_lamports: Some(50000),
1073 allowed_programs: None, allowed_tokens: None,
1075 fee_margin_percentage: Some(10.0),
1076 allowed_accounts: None,
1077 disallowed_accounts: None,
1078 swap_config: None,
1079 }));
1080
1081 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1082
1083 assert!(result.is_ok());
1084 let response = result.unwrap();
1085 assert_eq!(response.status(), 201);
1086
1087 let body = to_bytes(response.into_body()).await.unwrap();
1088 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1089
1090 assert!(api_response.success);
1091 let data = api_response.data.unwrap();
1092 assert_eq!(data.id, "test-solana-relayer");
1093 assert_eq!(data.network_type, RelayerNetworkType::Solana);
1094 assert_eq!(data.name, "Test Solana Relayer");
1095
1096 assert!(data.policies.is_some());
1098 let policies = data.policies.unwrap();
1100 if let RelayerNetworkPolicyResponse::Solana(solana_policy) = policies {
1101 assert_eq!(
1102 solana_policy.fee_payment_strategy,
1103 Some(SolanaFeePaymentStrategy::Relayer)
1104 );
1105 assert_eq!(solana_policy.min_balance, 5000000);
1106 assert_eq!(solana_policy.max_signatures, Some(10));
1107 assert_eq!(solana_policy.max_tx_data_size, 1232);
1108 assert_eq!(solana_policy.max_allowed_fee_lamports, Some(50000));
1109 } else {
1110 panic!("Expected Solana policies");
1111 }
1112 cleanup_test_env();
1113 }
1114
1115 #[actix_web::test]
1116 async fn test_create_relayer_with_stellar_policies() {
1117 let _lock = ENV_MUTEX.lock().await;
1118 setup_test_env();
1119 let network = create_mock_stellar_network();
1120 let signer = create_mock_signer();
1121 let app_state =
1122 create_mock_app_state(None, Some(vec![signer]), Some(vec![network]), None, None).await;
1123
1124 let mut request = create_test_relayer_create_request(
1125 Some("test-stellar-relayer".to_string()),
1126 "Test Stellar Relayer",
1127 "test",
1128 "test",
1129 None,
1130 );
1131
1132 request.network_type = RelayerNetworkType::Stellar;
1134 request.policies = Some(CreateRelayerPolicyRequest::Stellar(RelayerStellarPolicy {
1135 min_balance: Some(10000000),
1136 max_fee: Some(100),
1137 timeout_seconds: Some(30),
1138 }));
1139
1140 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1141
1142 assert!(result.is_ok());
1143 let response = result.unwrap();
1144 assert_eq!(response.status(), 201);
1145
1146 let body = to_bytes(response.into_body()).await.unwrap();
1147 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1148
1149 assert!(api_response.success);
1150 let data = api_response.data.unwrap();
1151 assert_eq!(data.id, "test-stellar-relayer");
1152 assert_eq!(data.network_type, RelayerNetworkType::Stellar);
1153
1154 assert!(data.policies.is_some());
1156 cleanup_test_env();
1157 }
1158
1159 #[actix_web::test]
1160 async fn test_create_relayer_with_policy_type_mismatch() {
1161 let _lock = ENV_MUTEX.lock().await;
1162 setup_test_env();
1163 let network = create_mock_network();
1164 let signer = create_mock_signer();
1165 let app_state =
1166 create_mock_app_state(None, Some(vec![signer]), Some(vec![network]), None, None).await;
1167
1168 let mut request = create_test_relayer_create_request(
1169 Some("test-mismatch-relayer".to_string()),
1170 "Test Mismatch Relayer",
1171 "test",
1172 "test",
1173 None,
1174 );
1175
1176 request.network_type = RelayerNetworkType::Evm;
1178 request.policies = Some(CreateRelayerPolicyRequest::Solana(
1179 RelayerSolanaPolicy::default(),
1180 ));
1181
1182 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1183
1184 assert!(result.is_err());
1185 if let Err(ApiError::BadRequest(msg)) = result {
1186 assert!(msg.contains("Policy type does not match relayer network type"));
1187 } else {
1188 panic!("Expected BadRequest error for policy type mismatch");
1189 }
1190 cleanup_test_env();
1191 }
1192
1193 #[actix_web::test]
1194 async fn test_create_relayer_with_notification() {
1195 let _lock = ENV_MUTEX.lock().await;
1196 setup_test_env();
1197 let network = create_mock_network();
1198 let signer = create_mock_signer();
1199 let notification = create_mock_notification("test-notification".to_string());
1200 let app_state =
1201 create_mock_app_state(None, Some(vec![signer]), Some(vec![network]), None, None).await;
1202
1203 app_state
1205 .notification_repository
1206 .create(notification)
1207 .await
1208 .unwrap();
1209
1210 let request = create_test_relayer_create_request(
1211 Some("test-relayer".to_string()),
1212 "Test Relayer",
1213 "test", "test", Some("test-notification".to_string()),
1216 );
1217
1218 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1219
1220 assert!(result.is_ok());
1221 let response = result.unwrap();
1222 assert_eq!(response.status(), 201);
1223 let body = to_bytes(response.into_body()).await.unwrap();
1224 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1225
1226 assert!(api_response.success);
1227 let data = api_response.data.unwrap();
1228 assert_eq!(data.notification_id, Some("test-notification".to_string()));
1229 cleanup_test_env();
1230 }
1231
1232 #[actix_web::test]
1233 async fn test_create_relayer_nonexistent_signer() {
1234 let network = create_mock_network();
1235 let app_state = create_mock_app_state(None, None, Some(vec![network]), None, None).await;
1236
1237 let request = create_test_relayer_create_request(
1238 Some("test-relayer".to_string()),
1239 "Test Relayer",
1240 "test", "nonexistent-signer",
1242 None,
1243 );
1244
1245 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1246
1247 assert!(result.is_err());
1248 if let Err(ApiError::NotFound(msg)) = result {
1249 assert!(msg.contains("Signer with ID nonexistent-signer not found"));
1250 } else {
1251 panic!("Expected NotFound error for nonexistent signer");
1252 }
1253 }
1254
1255 #[actix_web::test]
1256 async fn test_create_relayer_nonexistent_network() {
1257 let signer = create_mock_signer();
1258 let app_state = create_mock_app_state(None, Some(vec![signer]), None, None, None).await;
1259
1260 let request = create_test_relayer_create_request(
1261 Some("test-relayer".to_string()),
1262 "Test Relayer",
1263 "nonexistent-network",
1264 "test", None,
1266 );
1267
1268 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1269
1270 assert!(result.is_err());
1271 if let Err(ApiError::BadRequest(msg)) = result {
1272 assert!(msg.contains("Network 'nonexistent-network' not found"));
1273 assert!(msg.contains("network configuration exists"));
1274 } else {
1275 panic!("Expected BadRequest error for nonexistent network");
1276 }
1277 }
1278
1279 #[actix_web::test]
1280 async fn test_create_relayer_signer_already_in_use() {
1281 let network = create_mock_network();
1282 let signer = create_mock_signer();
1283 let mut existing_relayer = create_mock_relayer("existing-relayer".to_string(), false);
1284 existing_relayer.signer_id = "test".to_string(); existing_relayer.network = "test".to_string(); let app_state = create_mock_app_state(
1287 Some(vec![existing_relayer]),
1288 Some(vec![signer]),
1289 Some(vec![network]),
1290 None,
1291 None,
1292 )
1293 .await;
1294
1295 let request = create_test_relayer_create_request(
1296 Some("test-relayer".to_string()),
1297 "Test Relayer",
1298 "test", "test", None,
1301 );
1302
1303 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1304
1305 assert!(result.is_err());
1306 if let Err(ApiError::BadRequest(msg)) = result {
1307 assert!(msg.contains("signer 'test' is already in use"));
1308 assert!(msg.contains("relayer 'existing-relayer'"));
1309 assert!(msg.contains("network 'test'"));
1310 assert!(msg.contains("security reasons"));
1311 } else {
1312 panic!("Expected BadRequest error for signer already in use");
1313 }
1314 }
1315
1316 #[actix_web::test]
1317 async fn test_create_relayer_nonexistent_notification() {
1318 let network = create_mock_network();
1319 let signer = create_mock_signer();
1320 let app_state =
1321 create_mock_app_state(None, Some(vec![signer]), Some(vec![network]), None, None).await;
1322
1323 let request = create_test_relayer_create_request(
1324 Some("test-relayer".to_string()),
1325 "Test Relayer",
1326 "test", "test", Some("nonexistent-notification".to_string()),
1329 );
1330
1331 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1332
1333 assert!(result.is_err());
1334 if let Err(ApiError::NotFound(msg)) = result {
1335 assert!(msg.contains("Notification with ID 'nonexistent-notification' not found"));
1336 } else {
1337 panic!("Expected NotFound error for nonexistent notification");
1338 }
1339 }
1340
1341 #[actix_web::test]
1344 async fn test_list_relayers_success() {
1345 let relayer1 = create_mock_relayer("relayer-1".to_string(), false);
1346 let relayer2 = create_mock_relayer("relayer-2".to_string(), false);
1347 let app_state =
1348 create_mock_app_state(Some(vec![relayer1, relayer2]), None, None, None, None).await;
1349
1350 let query = PaginationQuery {
1351 page: 1,
1352 per_page: 10,
1353 };
1354
1355 let result = list_relayers(query, actix_web::web::ThinData(app_state)).await;
1356
1357 assert!(result.is_ok());
1358 let response = result.unwrap();
1359 assert_eq!(response.status(), 200);
1360
1361 let body = to_bytes(response.into_body()).await.unwrap();
1362 let api_response: ApiResponse<Vec<RelayerResponse>> =
1363 serde_json::from_slice(&body).unwrap();
1364
1365 assert!(api_response.success);
1366 let data = api_response.data.unwrap();
1367 assert_eq!(data.len(), 2);
1368 }
1369
1370 #[actix_web::test]
1371 async fn test_list_relayers_empty() {
1372 let app_state = create_mock_app_state(None, None, None, None, None).await;
1373
1374 let query = PaginationQuery {
1375 page: 1,
1376 per_page: 10,
1377 };
1378
1379 let result = list_relayers(query, actix_web::web::ThinData(app_state)).await;
1380
1381 assert!(result.is_ok());
1382 let response = result.unwrap();
1383 assert_eq!(response.status(), 200);
1384
1385 let body = to_bytes(response.into_body()).await.unwrap();
1386 let api_response: ApiResponse<Vec<RelayerResponse>> =
1387 serde_json::from_slice(&body).unwrap();
1388
1389 assert!(api_response.success);
1390 let data = api_response.data.unwrap();
1391 assert_eq!(data.len(), 0);
1392 }
1393
1394 #[actix_web::test]
1397 async fn test_get_relayer_success() {
1398 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1399 let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1400
1401 let result = get_relayer(
1402 "test-relayer".to_string(),
1403 actix_web::web::ThinData(app_state),
1404 )
1405 .await;
1406
1407 assert!(result.is_ok());
1408 let response = result.unwrap();
1409 assert_eq!(response.status(), 200);
1410
1411 let body = to_bytes(response.into_body()).await.unwrap();
1412 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1413
1414 assert!(api_response.success);
1415 let data = api_response.data.unwrap();
1416 assert_eq!(data.id, "test-relayer");
1417 assert_eq!(data.name, "Relayer test-relayer"); }
1419
1420 #[actix_web::test]
1421 async fn test_get_relayer_not_found() {
1422 let app_state = create_mock_app_state(None, None, None, None, None).await;
1423
1424 let result = get_relayer(
1425 "nonexistent".to_string(),
1426 actix_web::web::ThinData(app_state),
1427 )
1428 .await;
1429
1430 assert!(result.is_err());
1431 if let Err(ApiError::NotFound(msg)) = result {
1432 assert!(msg.contains("Relayer with ID nonexistent not found"));
1433 } else {
1434 panic!("Expected NotFound error");
1435 }
1436 }
1437
1438 #[actix_web::test]
1441 async fn test_update_relayer_success() {
1442 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1443 let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1444
1445 let patch = serde_json::json!({
1446 "name": "Updated Relayer Name",
1447 "paused": true
1448 });
1449
1450 let result = update_relayer(
1451 "test-relayer".to_string(),
1452 patch,
1453 actix_web::web::ThinData(app_state),
1454 )
1455 .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<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1463
1464 assert!(api_response.success);
1465 let data = api_response.data.unwrap();
1466 assert_eq!(data.name, "Updated Relayer Name");
1467 assert!(data.paused);
1468 }
1469
1470 #[actix_web::test]
1471 async fn test_update_relayer_system_disabled() {
1472 let mut relayer = create_mock_relayer("disabled-relayer".to_string(), false);
1473 relayer.system_disabled = true;
1474 let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1475
1476 let patch = serde_json::json!({
1477 "name": "Updated Name"
1478 });
1479
1480 let result = update_relayer(
1481 "disabled-relayer".to_string(),
1482 patch,
1483 actix_web::web::ThinData(app_state),
1484 )
1485 .await;
1486
1487 assert!(result.is_err());
1488 if let Err(ApiError::BadRequest(msg)) = result {
1489 assert!(msg.contains("Relayer is disabled"));
1490 } else {
1491 panic!("Expected BadRequest error for disabled relayer");
1492 }
1493 }
1494
1495 #[actix_web::test]
1496 async fn test_update_relayer_invalid_patch() {
1497 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1498 let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1499
1500 let patch = serde_json::json!({
1501 "invalid_field": "value"
1502 });
1503
1504 let result = update_relayer(
1505 "test-relayer".to_string(),
1506 patch,
1507 actix_web::web::ThinData(app_state),
1508 )
1509 .await;
1510
1511 assert!(result.is_err());
1512 if let Err(ApiError::BadRequest(msg)) = result {
1513 assert!(msg.contains("Invalid update request"));
1514 } else {
1515 panic!("Expected BadRequest error for invalid patch");
1516 }
1517 }
1518
1519 #[actix_web::test]
1520 async fn test_update_relayer_nonexistent() {
1521 let app_state = create_mock_app_state(None, None, None, None, None).await;
1522
1523 let patch = serde_json::json!({
1524 "name": "Updated Name"
1525 });
1526
1527 let result = update_relayer(
1528 "nonexistent-relayer".to_string(),
1529 patch,
1530 actix_web::web::ThinData(app_state),
1531 )
1532 .await;
1533
1534 assert!(result.is_err());
1535 if let Err(ApiError::NotFound(msg)) = result {
1536 assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
1537 } else {
1538 panic!("Expected NotFound error for nonexistent relayer");
1539 }
1540 }
1541
1542 #[actix_web::test]
1543 async fn test_update_relayer_set_evm_policies() {
1544 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1545 let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1546
1547 let patch = serde_json::json!({
1548 "policies": {
1549 "gas_price_cap": 50000000000u64,
1550 "min_balance": 1000000000000000000u64,
1551 "eip1559_pricing": true,
1552 "private_transactions": false,
1553 "gas_limit_estimation": true,
1554 "whitelist_receivers": ["0x1234567890123456789012345678901234567890"]
1555 }
1556 });
1557
1558 let result = update_relayer(
1559 "test-relayer".to_string(),
1560 patch,
1561 actix_web::web::ThinData(app_state),
1562 )
1563 .await;
1564
1565 assert!(result.is_ok());
1566 let response = result.unwrap();
1567 assert_eq!(response.status(), 200);
1568
1569 let body = to_bytes(response.into_body()).await.unwrap();
1570 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1571
1572 assert!(api_response.success);
1573 let data = api_response.data.unwrap();
1574
1575 assert!(data.policies.is_some());
1578 }
1579
1580 #[actix_web::test]
1581 async fn test_update_relayer_partial_policy_update() {
1582 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1583 let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1584
1585 let patch1 = serde_json::json!({
1587 "policies": {
1588 "gas_price_cap": 30000000000u64,
1589 "min_balance": 500000000000000000u64,
1590 "eip1559_pricing": false
1591 }
1592 });
1593
1594 let result1 = update_relayer(
1595 "test-relayer".to_string(),
1596 patch1,
1597 actix_web::web::ThinData(app_state),
1598 )
1599 .await;
1600
1601 assert!(result1.is_ok());
1602
1603 let relayer2 = create_mock_relayer("test-relayer".to_string(), false);
1605 let app_state2 = create_mock_app_state(Some(vec![relayer2]), None, None, None, None).await;
1606
1607 let patch2 = serde_json::json!({
1609 "policies": {
1610 "gas_price_cap": 60000000000u64
1611 }
1612 });
1613
1614 let result2 = update_relayer(
1615 "test-relayer".to_string(),
1616 patch2,
1617 actix_web::web::ThinData(app_state2),
1618 )
1619 .await;
1620
1621 assert!(result2.is_ok());
1622 let response = result2.unwrap();
1623 assert_eq!(response.status(), 200);
1624
1625 let body = to_bytes(response.into_body()).await.unwrap();
1626 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1627
1628 assert!(api_response.success);
1629 let data = api_response.data.unwrap();
1630
1631 assert!(data.policies.is_some());
1633 }
1634
1635 #[actix_web::test]
1636 async fn test_update_relayer_unset_notification() {
1637 let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
1638 relayer.notification_id = Some("test-notification".to_string());
1639 let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1640
1641 let patch = serde_json::json!({
1642 "notification_id": null
1643 });
1644
1645 let result = update_relayer(
1646 "test-relayer".to_string(),
1647 patch,
1648 actix_web::web::ThinData(app_state),
1649 )
1650 .await;
1651
1652 assert!(result.is_ok());
1653 let response = result.unwrap();
1654 assert_eq!(response.status(), 200);
1655
1656 let body = to_bytes(response.into_body()).await.unwrap();
1657 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1658
1659 assert!(api_response.success);
1660 let data = api_response.data.unwrap();
1661 assert_eq!(data.notification_id, None);
1662 }
1663
1664 #[actix_web::test]
1665 async fn test_update_relayer_unset_custom_rpc_urls() {
1666 let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
1667 relayer.custom_rpc_urls = Some(vec![crate::models::RpcConfig {
1668 url: "https://custom-rpc.example.com".to_string(),
1669 weight: 50,
1670 }]);
1671 let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1672
1673 let patch = serde_json::json!({
1674 "custom_rpc_urls": null
1675 });
1676
1677 let result = update_relayer(
1678 "test-relayer".to_string(),
1679 patch,
1680 actix_web::web::ThinData(app_state),
1681 )
1682 .await;
1683
1684 assert!(result.is_ok());
1685 let response = result.unwrap();
1686 assert_eq!(response.status(), 200);
1687
1688 let body = to_bytes(response.into_body()).await.unwrap();
1689 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1690
1691 assert!(api_response.success);
1692 let data = api_response.data.unwrap();
1693 assert_eq!(data.custom_rpc_urls, None);
1694 }
1695
1696 #[actix_web::test]
1697 async fn test_update_relayer_set_custom_rpc_urls() {
1698 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1699 let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1700
1701 let patch = serde_json::json!({
1702 "custom_rpc_urls": [
1703 {
1704 "url": "https://rpc1.example.com",
1705 "weight": 80
1706 },
1707 {
1708 "url": "https://rpc2.example.com",
1709 "weight": 60
1710 }
1711 ]
1712 });
1713
1714 let result = update_relayer(
1715 "test-relayer".to_string(),
1716 patch,
1717 actix_web::web::ThinData(app_state),
1718 )
1719 .await;
1720
1721 assert!(result.is_ok());
1722 let response = result.unwrap();
1723 assert_eq!(response.status(), 200);
1724
1725 let body = to_bytes(response.into_body()).await.unwrap();
1726 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1727
1728 assert!(api_response.success);
1729 let data = api_response.data.unwrap();
1730
1731 assert!(data.custom_rpc_urls.is_some());
1732 let rpc_urls = data.custom_rpc_urls.unwrap();
1733 assert_eq!(rpc_urls.len(), 2);
1734 assert_eq!(rpc_urls[0].url, "https://rpc1.example.com");
1735 assert_eq!(rpc_urls[0].weight, 80);
1736 assert_eq!(rpc_urls[1].url, "https://rpc2.example.com");
1737 assert_eq!(rpc_urls[1].weight, 60);
1738 }
1739
1740 #[actix_web::test]
1741 async fn test_update_relayer_clear_policies() {
1742 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1743 let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1744
1745 let patch = serde_json::json!({
1746 "policies": null
1747 });
1748
1749 let result = update_relayer(
1750 "test-relayer".to_string(),
1751 patch,
1752 actix_web::web::ThinData(app_state),
1753 )
1754 .await;
1755
1756 assert!(result.is_ok());
1757 let response = result.unwrap();
1758 assert_eq!(response.status(), 200);
1759
1760 let body = to_bytes(response.into_body()).await.unwrap();
1761 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1762
1763 assert!(api_response.success);
1764 let data = api_response.data.unwrap();
1765 assert_eq!(data.policies, None);
1766 }
1767
1768 #[actix_web::test]
1769 async fn test_update_relayer_invalid_policy_structure() {
1770 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1771 let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1772
1773 let patch = serde_json::json!({
1774 "policies": {
1775 "invalid_field_name": "some_value"
1776 }
1777 });
1778
1779 let result = update_relayer(
1780 "test-relayer".to_string(),
1781 patch,
1782 actix_web::web::ThinData(app_state),
1783 )
1784 .await;
1785
1786 assert!(result.is_err());
1787 if let Err(ApiError::BadRequest(msg)) = result {
1788 assert!(msg.contains("Invalid policy"));
1789 } else {
1790 panic!("Expected BadRequest error for invalid policy structure");
1791 }
1792 }
1793
1794 #[actix_web::test]
1795 async fn test_update_relayer_invalid_evm_policy_values() {
1796 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1797 let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1798
1799 let patch = serde_json::json!({
1800 "policies": {
1801 "gas_price_cap": "invalid_number",
1802 "min_balance": -1
1803 }
1804 });
1805
1806 let result = update_relayer(
1807 "test-relayer".to_string(),
1808 patch,
1809 actix_web::web::ThinData(app_state),
1810 )
1811 .await;
1812
1813 assert!(result.is_err());
1814 if let Err(ApiError::BadRequest(msg)) = result {
1815 assert!(msg.contains("Invalid policy") || msg.contains("Invalid update request"));
1816 } else {
1817 panic!("Expected BadRequest error for invalid policy values");
1818 }
1819 }
1820
1821 #[actix_web::test]
1822 async fn test_update_relayer_multiple_fields_at_once() {
1823 let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
1824 relayer.notification_id = Some("old-notification".to_string());
1825 let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1826
1827 let patch = serde_json::json!({
1828 "name": "Multi-Update Relayer",
1829 "paused": true,
1830 "notification_id": null,
1831 "policies": {
1832 "gas_price_cap": 40000000000u64,
1833 "eip1559_pricing": true
1834 },
1835 "custom_rpc_urls": [
1836 {
1837 "url": "https://new-rpc.example.com",
1838 "weight": 90
1839 }
1840 ]
1841 });
1842
1843 let result = update_relayer(
1844 "test-relayer".to_string(),
1845 patch,
1846 actix_web::web::ThinData(app_state),
1847 )
1848 .await;
1849
1850 assert!(result.is_ok());
1851 let response = result.unwrap();
1852 assert_eq!(response.status(), 200);
1853
1854 let body = to_bytes(response.into_body()).await.unwrap();
1855 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1856
1857 assert!(api_response.success);
1858 let data = api_response.data.unwrap();
1859
1860 assert_eq!(data.name, "Multi-Update Relayer");
1862 assert!(data.paused);
1863 assert_eq!(data.notification_id, None);
1864
1865 assert!(data.policies.is_some());
1867 assert!(data.custom_rpc_urls.is_some());
1868 let rpc_urls = data.custom_rpc_urls.unwrap();
1869 assert_eq!(rpc_urls.len(), 1);
1870 assert_eq!(rpc_urls[0].url, "https://new-rpc.example.com");
1871 assert_eq!(rpc_urls[0].weight, 90);
1872 }
1873
1874 #[actix_web::test]
1875 async fn test_update_relayer_solana_policies() {
1876 use crate::models::{
1877 NetworkType, RelayerNetworkPolicy, RelayerSolanaPolicy, SolanaFeePaymentStrategy,
1878 };
1879
1880 let mut solana_relayer = create_mock_relayer("test-solana-relayer".to_string(), false);
1882 solana_relayer.network_type = NetworkType::Solana;
1883 solana_relayer.policies = RelayerNetworkPolicy::Solana(RelayerSolanaPolicy::default());
1884
1885 let app_state =
1886 create_mock_app_state(Some(vec![solana_relayer]), None, None, None, None).await;
1887
1888 let patch = serde_json::json!({
1889 "policies": {
1890 "fee_payment_strategy": "user",
1891 "min_balance": 2000000,
1892 "max_signatures": 5,
1893 "max_tx_data_size": 800,
1894 "max_allowed_fee_lamports": 25000,
1895 "fee_margin_percentage": 15.0
1896 }
1897 });
1898
1899 let result = update_relayer(
1900 "test-solana-relayer".to_string(),
1901 patch,
1902 actix_web::web::ThinData(app_state),
1903 )
1904 .await;
1905
1906 assert!(result.is_ok());
1907 let response = result.unwrap();
1908 assert_eq!(response.status(), 200);
1909
1910 let body = to_bytes(response.into_body()).await.unwrap();
1911 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1912
1913 assert!(api_response.success);
1914 let data = api_response.data.unwrap();
1915
1916 assert!(data.policies.is_some());
1918 let policies = data.policies.unwrap();
1919 if let RelayerNetworkPolicyResponse::Solana(solana_policy) = policies {
1920 assert_eq!(
1921 solana_policy.fee_payment_strategy,
1922 Some(SolanaFeePaymentStrategy::User)
1923 );
1924 assert_eq!(solana_policy.min_balance, 2000000);
1925 assert_eq!(solana_policy.max_signatures, Some(5));
1926 assert_eq!(solana_policy.max_tx_data_size, 800);
1927 assert_eq!(solana_policy.max_allowed_fee_lamports, Some(25000));
1928 assert_eq!(solana_policy.fee_margin_percentage, Some(15.0));
1929 } else {
1930 panic!("Expected Solana policies in response");
1931 }
1932 }
1933
1934 #[actix_web::test]
1937 async fn test_delete_relayer_success() {
1938 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1939 let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1940
1941 let result = delete_relayer(
1942 "test-relayer".to_string(),
1943 actix_web::web::ThinData(app_state),
1944 )
1945 .await;
1946
1947 assert!(result.is_ok());
1948 let response = result.unwrap();
1949 assert_eq!(response.status(), 200);
1950
1951 let body = to_bytes(response.into_body()).await.unwrap();
1952 let api_response: ApiResponse<String> = serde_json::from_slice(&body).unwrap();
1953
1954 assert!(api_response.success);
1955 let data = api_response.data.unwrap();
1956 assert!(data.contains("Relayer deleted successfully"));
1957 }
1958
1959 #[actix_web::test]
1960 async fn test_delete_relayer_with_transactions() {
1961 let relayer = create_mock_relayer("relayer-with-tx".to_string(), false);
1962 let mut transaction = create_mock_transaction();
1963 transaction.id = "test-tx".to_string();
1964 transaction.relayer_id = "relayer-with-tx".to_string();
1965 let app_state = create_mock_app_state(
1966 Some(vec![relayer]),
1967 None,
1968 None,
1969 None,
1970 Some(vec![transaction]),
1971 )
1972 .await;
1973
1974 let result = delete_relayer(
1975 "relayer-with-tx".to_string(),
1976 actix_web::web::ThinData(app_state),
1977 )
1978 .await;
1979
1980 assert!(result.is_err());
1981 if let Err(ApiError::BadRequest(msg)) = result {
1982 assert!(msg.contains("Cannot delete relayer 'relayer-with-tx'"));
1983 assert!(msg.contains("has 1 transaction(s)"));
1984 assert!(msg.contains("wait for all transactions to complete"));
1985 } else {
1986 panic!("Expected BadRequest error for relayer with transactions");
1987 }
1988 }
1989
1990 #[actix_web::test]
1991 async fn test_delete_relayer_nonexistent() {
1992 let app_state = create_mock_app_state(None, None, None, None, None).await;
1993
1994 let result = delete_relayer(
1995 "nonexistent-relayer".to_string(),
1996 actix_web::web::ThinData(app_state),
1997 )
1998 .await;
1999
2000 assert!(result.is_err());
2001 if let Err(ApiError::NotFound(msg)) = result {
2002 assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
2003 } else {
2004 panic!("Expected NotFound error for nonexistent relayer");
2005 }
2006 }
2007
2008 #[actix_web::test]
2009 async fn test_sign_transaction_success() {
2010 let _lock = ENV_MUTEX.lock().await;
2011 setup_test_env();
2012 let network = create_mock_stellar_network();
2013 let signer = create_mock_signer();
2014 let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
2015 relayer.network_type = NetworkType::Stellar;
2016 let app_state = create_mock_app_state(
2017 Some(vec![relayer]),
2018 Some(vec![signer]),
2019 Some(vec![network]),
2020 None,
2021 None,
2022 )
2023 .await;
2024
2025 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2026 unsigned_xdr: "test-unsigned-xdr".to_string(),
2027 });
2028
2029 let result = sign_transaction(
2030 "test-relayer".to_string(),
2031 request,
2032 actix_web::web::ThinData(app_state),
2033 )
2034 .await;
2035
2036 assert!(result.is_err());
2039 cleanup_test_env();
2040 }
2041
2042 #[actix_web::test]
2043 async fn test_sign_transaction_relayer_not_found() {
2044 let app_state = create_mock_app_state(None, None, None, None, None).await;
2045
2046 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2047 unsigned_xdr: "test-unsigned-xdr".to_string(),
2048 });
2049
2050 let result = sign_transaction(
2051 "nonexistent-relayer".to_string(),
2052 request,
2053 actix_web::web::ThinData(app_state),
2054 )
2055 .await;
2056
2057 assert!(result.is_err());
2058 if let Err(ApiError::NotFound(msg)) = result {
2059 assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
2060 } else {
2061 panic!("Expected NotFound error for nonexistent relayer");
2062 }
2063 }
2064
2065 #[actix_web::test]
2066 async fn test_sign_transaction_relayer_disabled() {
2067 let mut relayer = create_mock_relayer("disabled-relayer".to_string(), false);
2068 relayer.paused = true;
2069 let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
2070
2071 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2072 unsigned_xdr: "test-unsigned-xdr".to_string(),
2073 });
2074
2075 let result = sign_transaction(
2076 "disabled-relayer".to_string(),
2077 request,
2078 actix_web::web::ThinData(app_state),
2079 )
2080 .await;
2081
2082 assert!(result.is_err());
2083 if let Err(ApiError::ForbiddenError(msg)) = result {
2084 assert!(msg.contains("Relayer paused"));
2085 } else {
2086 panic!("Expected ForbiddenError for paused relayer");
2087 }
2088 }
2089
2090 #[actix_web::test]
2091 async fn test_sign_transaction_system_disabled() {
2092 let mut relayer = create_mock_relayer("system-disabled-relayer".to_string(), false);
2093 relayer.system_disabled = true;
2094 let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
2095
2096 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2097 unsigned_xdr: "test-unsigned-xdr".to_string(),
2098 });
2099
2100 let result = sign_transaction(
2101 "system-disabled-relayer".to_string(),
2102 request,
2103 actix_web::web::ThinData(app_state),
2104 )
2105 .await;
2106
2107 assert!(result.is_err());
2108 if let Err(ApiError::ForbiddenError(msg)) = result {
2109 assert!(msg.contains("Relayer disabled"));
2110 } else {
2111 panic!("Expected ForbiddenError for system disabled relayer");
2112 }
2113 }
2114}