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