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