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