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 include_revert_data: None,
1084 gas_price_cap: Some(50000000000),
1085 min_balance: Some(1000000000000000000),
1086 eip1559_pricing: Some(true),
1087 private_transactions: Some(false),
1088 gas_limit_estimation: Some(true),
1089 whitelist_receivers: Some(vec![
1090 "0x1234567890123456789012345678901234567890".to_string()
1091 ]),
1092 }));
1093
1094 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1095
1096 assert!(result.is_ok());
1097 let response = result.unwrap();
1098 assert_eq!(response.status(), 201);
1099
1100 let body = to_bytes(response.into_body()).await.unwrap();
1101 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1102
1103 assert!(api_response.success);
1104 let data = api_response.data.unwrap();
1105 assert_eq!(data.id, "test-relayer-policies");
1106 assert_eq!(data.name, "Test Relayer with Policies");
1107 assert_eq!(data.network, "test");
1108
1109 assert!(data.policies.is_some());
1111 cleanup_test_env();
1112 }
1113
1114 #[actix_web::test]
1115 async fn test_create_relayer_with_partial_evm_policies() {
1116 let _lock = ENV_MUTEX.lock().await;
1117 setup_test_env();
1118 let network = create_mock_network();
1119 let signer = create_mock_signer();
1120 let app_state = create_mock_app_state(
1121 None,
1122 None,
1123 Some(vec![signer]),
1124 Some(vec![network]),
1125 None,
1126 None,
1127 )
1128 .await;
1129
1130 let mut request = create_test_relayer_create_request(
1131 Some("test-relayer-partial".to_string()),
1132 "Test Relayer with Partial Policies",
1133 "test",
1134 "test",
1135 None,
1136 );
1137
1138 request.policies = Some(CreateRelayerPolicyRequest::Evm(RelayerEvmPolicy {
1140 include_revert_data: None,
1141 gas_price_cap: Some(30000000000),
1142 eip1559_pricing: Some(false),
1143 min_balance: None,
1144 private_transactions: None,
1145 gas_limit_estimation: None,
1146 whitelist_receivers: None,
1147 }));
1148
1149 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1150
1151 assert!(result.is_ok());
1152 let response = result.unwrap();
1153 assert_eq!(response.status(), 201);
1154
1155 let body = to_bytes(response.into_body()).await.unwrap();
1156 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1157
1158 assert!(api_response.success);
1159 let data = api_response.data.unwrap();
1160 assert_eq!(data.id, "test-relayer-partial");
1161
1162 assert!(data.policies.is_some());
1164 cleanup_test_env();
1165 }
1166
1167 #[actix_web::test]
1168 async fn test_create_relayer_with_solana_policies() {
1169 let _lock = ENV_MUTEX.lock().await;
1170 setup_test_env();
1171 let network = create_mock_solana_network();
1172 let signer = create_mock_signer();
1173 let app_state = create_mock_app_state(
1174 None,
1175 None,
1176 Some(vec![signer]),
1177 Some(vec![network]),
1178 None,
1179 None,
1180 )
1181 .await;
1182
1183 let mut request = create_test_relayer_create_request(
1184 Some("test-solana-relayer".to_string()),
1185 "Test Solana Relayer",
1186 "test",
1187 "test",
1188 None,
1189 );
1190
1191 request.network_type = RelayerNetworkType::Solana;
1193 request.policies = Some(CreateRelayerPolicyRequest::Solana(RelayerSolanaPolicy {
1194 fee_payment_strategy: Some(SolanaFeePaymentStrategy::Relayer),
1195 min_balance: Some(5000000),
1196 max_signatures: Some(10),
1197 max_tx_data_size: Some(1232),
1198 max_allowed_fee_lamports: Some(50000),
1199 allowed_programs: None, allowed_tokens: None,
1201 fee_margin_percentage: Some(10.0),
1202 allowed_accounts: None,
1203 disallowed_accounts: None,
1204 swap_config: None,
1205 }));
1206
1207 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1208
1209 assert!(result.is_ok());
1210 let response = result.unwrap();
1211 assert_eq!(response.status(), 201);
1212
1213 let body = to_bytes(response.into_body()).await.unwrap();
1214 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1215
1216 assert!(api_response.success);
1217 let data = api_response.data.unwrap();
1218 assert_eq!(data.id, "test-solana-relayer");
1219 assert_eq!(data.network_type, RelayerNetworkType::Solana);
1220 assert_eq!(data.name, "Test Solana Relayer");
1221
1222 assert!(data.policies.is_some());
1224 let policies = data.policies.unwrap();
1226 if let RelayerNetworkPolicyResponse::Solana(solana_policy) = policies {
1227 assert_eq!(
1228 solana_policy.fee_payment_strategy,
1229 Some(SolanaFeePaymentStrategy::Relayer)
1230 );
1231 assert_eq!(solana_policy.min_balance, 5000000);
1232 assert_eq!(solana_policy.max_signatures, Some(10));
1233 assert_eq!(solana_policy.max_tx_data_size, 1232);
1234 assert_eq!(solana_policy.max_allowed_fee_lamports, Some(50000));
1235 } else {
1236 panic!("Expected Solana policies");
1237 }
1238 cleanup_test_env();
1239 }
1240
1241 #[actix_web::test]
1242 async fn test_create_relayer_with_stellar_policies() {
1243 let _lock = ENV_MUTEX.lock().await;
1244 setup_test_env();
1245 let network = create_mock_stellar_network();
1246 let signer = create_mock_signer();
1247 let app_state = create_mock_app_state(
1248 None,
1249 None,
1250 Some(vec![signer]),
1251 Some(vec![network]),
1252 None,
1253 None,
1254 )
1255 .await;
1256
1257 let mut request = create_test_relayer_create_request(
1258 Some("test-stellar-relayer".to_string()),
1259 "Test Stellar Relayer",
1260 "test",
1261 "test",
1262 None,
1263 );
1264
1265 request.network_type = RelayerNetworkType::Stellar;
1267 request.policies = Some(CreateRelayerPolicyRequest::Stellar(RelayerStellarPolicy {
1268 min_balance: Some(10000000),
1269 max_fee: Some(100),
1270 timeout_seconds: Some(30),
1271 concurrent_transactions: None,
1272 allowed_tokens: None,
1273 fee_payment_strategy: Some(StellarFeePaymentStrategy::Relayer),
1274 slippage_percentage: None,
1275 fee_margin_percentage: None,
1276 swap_config: None,
1277 }));
1278
1279 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1280
1281 assert!(result.is_ok());
1282 let response = result.unwrap();
1283 assert_eq!(response.status(), 201);
1284
1285 let body = to_bytes(response.into_body()).await.unwrap();
1286 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1287
1288 assert!(api_response.success);
1289 let data = api_response.data.unwrap();
1290 assert_eq!(data.id, "test-stellar-relayer");
1291 assert_eq!(data.network_type, RelayerNetworkType::Stellar);
1292
1293 assert!(data.policies.is_some());
1295 cleanup_test_env();
1296 }
1297
1298 #[actix_web::test]
1299 async fn test_create_relayer_with_policy_type_mismatch() {
1300 let _lock = ENV_MUTEX.lock().await;
1301 setup_test_env();
1302 let network = create_mock_network();
1303 let signer = create_mock_signer();
1304 let app_state = create_mock_app_state(
1305 None,
1306 None,
1307 Some(vec![signer]),
1308 Some(vec![network]),
1309 None,
1310 None,
1311 )
1312 .await;
1313
1314 let mut request = create_test_relayer_create_request(
1315 Some("test-mismatch-relayer".to_string()),
1316 "Test Mismatch Relayer",
1317 "test",
1318 "test",
1319 None,
1320 );
1321
1322 request.network_type = RelayerNetworkType::Evm;
1324 request.policies = Some(CreateRelayerPolicyRequest::Solana(
1325 RelayerSolanaPolicy::default(),
1326 ));
1327
1328 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1329
1330 assert!(result.is_err());
1331 if let Err(ApiError::BadRequest(msg)) = result {
1332 assert!(msg.contains("Policy type does not match relayer network type"));
1333 } else {
1334 panic!("Expected BadRequest error for policy type mismatch");
1335 }
1336 cleanup_test_env();
1337 }
1338
1339 #[actix_web::test]
1340 async fn test_create_relayer_with_notification() {
1341 let _lock = ENV_MUTEX.lock().await;
1342 setup_test_env();
1343 let network = create_mock_network();
1344 let signer = create_mock_signer();
1345 let notification = create_mock_notification("test-notification".to_string());
1346 let app_state = create_mock_app_state(
1347 None,
1348 None,
1349 Some(vec![signer]),
1350 Some(vec![network]),
1351 None,
1352 None,
1353 )
1354 .await;
1355
1356 app_state
1358 .notification_repository
1359 .create(notification)
1360 .await
1361 .unwrap();
1362
1363 let request = create_test_relayer_create_request(
1364 Some("test-relayer".to_string()),
1365 "Test Relayer",
1366 "test", "test", Some("test-notification".to_string()),
1369 );
1370
1371 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1372
1373 assert!(result.is_ok());
1374 let response = result.unwrap();
1375 assert_eq!(response.status(), 201);
1376 let body = to_bytes(response.into_body()).await.unwrap();
1377 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1378
1379 assert!(api_response.success);
1380 let data = api_response.data.unwrap();
1381 assert_eq!(data.notification_id, Some("test-notification".to_string()));
1382 cleanup_test_env();
1383 }
1384
1385 #[actix_web::test]
1386 async fn test_create_relayer_nonexistent_signer() {
1387 let network = create_mock_network();
1388 let app_state =
1389 create_mock_app_state(None, None, None, Some(vec![network]), None, None).await;
1390
1391 let request = create_test_relayer_create_request(
1392 Some("test-relayer".to_string()),
1393 "Test Relayer",
1394 "test", "nonexistent-signer",
1396 None,
1397 );
1398
1399 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1400
1401 assert!(result.is_err());
1402 if let Err(ApiError::NotFound(msg)) = result {
1403 assert!(msg.contains("Signer with ID nonexistent-signer not found"));
1404 } else {
1405 panic!("Expected NotFound error for nonexistent signer");
1406 }
1407 }
1408
1409 #[actix_web::test]
1410 async fn test_create_relayer_nonexistent_network() {
1411 let signer = create_mock_signer();
1412 let app_state =
1413 create_mock_app_state(None, None, Some(vec![signer]), None, None, None).await;
1414
1415 let request = create_test_relayer_create_request(
1416 Some("test-relayer".to_string()),
1417 "Test Relayer",
1418 "nonexistent-network",
1419 "test", None,
1421 );
1422
1423 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1424
1425 assert!(result.is_err());
1426 if let Err(ApiError::BadRequest(msg)) = result {
1427 assert!(msg.contains("Network 'nonexistent-network' not found"));
1428 assert!(msg.contains("network configuration exists"));
1429 } else {
1430 panic!("Expected BadRequest error for nonexistent network");
1431 }
1432 }
1433
1434 #[actix_web::test]
1435 async fn test_create_relayer_signer_already_in_use() {
1436 let network = create_mock_network();
1437 let signer = create_mock_signer();
1438 let mut existing_relayer = create_mock_relayer("existing-relayer".to_string(), false);
1439 existing_relayer.signer_id = "test".to_string(); existing_relayer.network = "test".to_string(); let app_state = create_mock_app_state(
1442 None,
1443 Some(vec![existing_relayer]),
1444 Some(vec![signer]),
1445 Some(vec![network]),
1446 None,
1447 None,
1448 )
1449 .await;
1450
1451 let request = create_test_relayer_create_request(
1452 Some("test-relayer".to_string()),
1453 "Test Relayer",
1454 "test", "test", None,
1457 );
1458
1459 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1460
1461 assert!(result.is_err());
1462 if let Err(ApiError::BadRequest(msg)) = result {
1463 assert!(msg.contains("signer 'test' is already in use"));
1464 assert!(msg.contains("relayer 'existing-relayer'"));
1465 assert!(msg.contains("network 'test'"));
1466 assert!(msg.contains("security reasons"));
1467 } else {
1468 panic!("Expected BadRequest error for signer already in use");
1469 }
1470 }
1471
1472 #[actix_web::test]
1473 async fn test_create_relayer_nonexistent_notification() {
1474 let network = create_mock_network();
1475 let signer = create_mock_signer();
1476 let app_state = create_mock_app_state(
1477 None,
1478 None,
1479 Some(vec![signer]),
1480 Some(vec![network]),
1481 None,
1482 None,
1483 )
1484 .await;
1485
1486 let request = create_test_relayer_create_request(
1487 Some("test-relayer".to_string()),
1488 "Test Relayer",
1489 "test", "test", Some("nonexistent-notification".to_string()),
1492 );
1493
1494 let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1495
1496 assert!(result.is_err());
1497 if let Err(ApiError::NotFound(msg)) = result {
1498 assert!(msg.contains("Notification with ID 'nonexistent-notification' not found"));
1499 } else {
1500 panic!("Expected NotFound error for nonexistent notification");
1501 }
1502 }
1503
1504 #[actix_web::test]
1507 async fn test_list_relayers_success() {
1508 let relayer1 = create_mock_relayer("relayer-1".to_string(), false);
1509 let relayer2 = create_mock_relayer("relayer-2".to_string(), false);
1510 let app_state =
1511 create_mock_app_state(None, Some(vec![relayer1, relayer2]), None, None, None, None)
1512 .await;
1513
1514 let query = PaginationQuery {
1515 page: 1,
1516 per_page: 10,
1517 };
1518
1519 let result = list_relayers(query, actix_web::web::ThinData(app_state)).await;
1520
1521 assert!(result.is_ok());
1522 let response = result.unwrap();
1523 assert_eq!(response.status(), 200);
1524
1525 let body = to_bytes(response.into_body()).await.unwrap();
1526 let api_response: ApiResponse<Vec<RelayerResponse>> =
1527 serde_json::from_slice(&body).unwrap();
1528
1529 assert!(api_response.success);
1530 let data = api_response.data.unwrap();
1531 assert_eq!(data.len(), 2);
1532 }
1533
1534 #[actix_web::test]
1535 async fn test_list_relayers_empty() {
1536 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
1537
1538 let query = PaginationQuery {
1539 page: 1,
1540 per_page: 10,
1541 };
1542
1543 let result = list_relayers(query, actix_web::web::ThinData(app_state)).await;
1544
1545 assert!(result.is_ok());
1546 let response = result.unwrap();
1547 assert_eq!(response.status(), 200);
1548
1549 let body = to_bytes(response.into_body()).await.unwrap();
1550 let api_response: ApiResponse<Vec<RelayerResponse>> =
1551 serde_json::from_slice(&body).unwrap();
1552
1553 assert!(api_response.success);
1554 let data = api_response.data.unwrap();
1555 assert_eq!(data.len(), 0);
1556 }
1557
1558 #[actix_web::test]
1561 async fn test_get_relayer_success() {
1562 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1563 let app_state =
1564 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1565
1566 let result = get_relayer(
1567 "test-relayer".to_string(),
1568 actix_web::web::ThinData(app_state),
1569 )
1570 .await;
1571
1572 assert!(result.is_ok());
1573 let response = result.unwrap();
1574 assert_eq!(response.status(), 200);
1575
1576 let body = to_bytes(response.into_body()).await.unwrap();
1577 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1578
1579 assert!(api_response.success);
1580 let data = api_response.data.unwrap();
1581 assert_eq!(data.id, "test-relayer");
1582 assert_eq!(data.name, "Relayer test-relayer"); }
1584
1585 #[actix_web::test]
1586 async fn test_get_relayer_not_found() {
1587 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
1588
1589 let result = get_relayer(
1590 "nonexistent".to_string(),
1591 actix_web::web::ThinData(app_state),
1592 )
1593 .await;
1594
1595 assert!(result.is_err());
1596 if let Err(ApiError::NotFound(msg)) = result {
1597 assert!(msg.contains("Relayer with ID nonexistent not found"));
1598 } else {
1599 panic!("Expected NotFound error");
1600 }
1601 }
1602
1603 #[actix_web::test]
1606 async fn test_update_relayer_success() {
1607 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1608 let app_state =
1609 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1610
1611 let patch = serde_json::json!({
1612 "name": "Updated Relayer Name",
1613 "paused": true
1614 });
1615
1616 let result = update_relayer(
1617 "test-relayer".to_string(),
1618 patch,
1619 actix_web::web::ThinData(app_state),
1620 )
1621 .await;
1622
1623 assert!(result.is_ok());
1624 let response = result.unwrap();
1625 assert_eq!(response.status(), 200);
1626
1627 let body = to_bytes(response.into_body()).await.unwrap();
1628 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1629
1630 assert!(api_response.success);
1631 let data = api_response.data.unwrap();
1632 assert_eq!(data.name, "Updated Relayer Name");
1633 assert!(data.paused);
1634 }
1635
1636 #[actix_web::test]
1637 async fn test_update_relayer_system_disabled() {
1638 let mut relayer = create_mock_relayer("disabled-relayer".to_string(), false);
1639 relayer.system_disabled = true;
1640 let app_state =
1641 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1642
1643 let patch = serde_json::json!({
1644 "name": "Updated Name"
1645 });
1646
1647 let result = update_relayer(
1648 "disabled-relayer".to_string(),
1649 patch,
1650 actix_web::web::ThinData(app_state),
1651 )
1652 .await;
1653
1654 assert!(result.is_err());
1655 if let Err(ApiError::BadRequest(msg)) = result {
1656 assert!(msg.contains("Relayer is disabled"));
1657 } else {
1658 panic!("Expected BadRequest error for disabled relayer");
1659 }
1660 }
1661
1662 #[actix_web::test]
1663 async fn test_update_relayer_invalid_patch() {
1664 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1665 let app_state =
1666 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1667
1668 let patch = serde_json::json!({
1669 "invalid_field": "value"
1670 });
1671
1672 let result = update_relayer(
1673 "test-relayer".to_string(),
1674 patch,
1675 actix_web::web::ThinData(app_state),
1676 )
1677 .await;
1678
1679 assert!(result.is_err());
1680 if let Err(ApiError::BadRequest(msg)) = result {
1681 assert!(msg.contains("Invalid update request"));
1682 } else {
1683 panic!("Expected BadRequest error for invalid patch");
1684 }
1685 }
1686
1687 #[actix_web::test]
1688 async fn test_update_relayer_nonexistent() {
1689 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
1690
1691 let patch = serde_json::json!({
1692 "name": "Updated Name"
1693 });
1694
1695 let result = update_relayer(
1696 "nonexistent-relayer".to_string(),
1697 patch,
1698 actix_web::web::ThinData(app_state),
1699 )
1700 .await;
1701
1702 assert!(result.is_err());
1703 if let Err(ApiError::NotFound(msg)) = result {
1704 assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
1705 } else {
1706 panic!("Expected NotFound error for nonexistent relayer");
1707 }
1708 }
1709
1710 #[actix_web::test]
1711 async fn test_update_relayer_set_evm_policies() {
1712 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1713 let app_state =
1714 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1715
1716 let patch = serde_json::json!({
1717 "policies": {
1718 "gas_price_cap": 50000000000u64,
1719 "min_balance": 1000000000000000000u64,
1720 "eip1559_pricing": true,
1721 "private_transactions": false,
1722 "gas_limit_estimation": true,
1723 "whitelist_receivers": ["0x1234567890123456789012345678901234567890"]
1724 }
1725 });
1726
1727 let result = update_relayer(
1728 "test-relayer".to_string(),
1729 patch,
1730 actix_web::web::ThinData(app_state),
1731 )
1732 .await;
1733
1734 assert!(result.is_ok());
1735 let response = result.unwrap();
1736 assert_eq!(response.status(), 200);
1737
1738 let body = to_bytes(response.into_body()).await.unwrap();
1739 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1740
1741 assert!(api_response.success);
1742 let data = api_response.data.unwrap();
1743
1744 assert!(data.policies.is_some());
1747 }
1748
1749 #[actix_web::test]
1750 async fn test_update_relayer_partial_policy_update() {
1751 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1752 let app_state =
1753 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1754
1755 let patch1 = serde_json::json!({
1757 "policies": {
1758 "gas_price_cap": 30000000000u64,
1759 "min_balance": 500000000000000000u64,
1760 "eip1559_pricing": false
1761 }
1762 });
1763
1764 let result1 = update_relayer(
1765 "test-relayer".to_string(),
1766 patch1,
1767 actix_web::web::ThinData(app_state),
1768 )
1769 .await;
1770
1771 assert!(result1.is_ok());
1772
1773 let relayer2 = create_mock_relayer("test-relayer".to_string(), false);
1775 let app_state2 =
1776 create_mock_app_state(None, Some(vec![relayer2]), None, None, None, None).await;
1777
1778 let patch2 = serde_json::json!({
1780 "policies": {
1781 "gas_price_cap": 60000000000u64
1782 }
1783 });
1784
1785 let result2 = update_relayer(
1786 "test-relayer".to_string(),
1787 patch2,
1788 actix_web::web::ThinData(app_state2),
1789 )
1790 .await;
1791
1792 assert!(result2.is_ok());
1793 let response = result2.unwrap();
1794 assert_eq!(response.status(), 200);
1795
1796 let body = to_bytes(response.into_body()).await.unwrap();
1797 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1798
1799 assert!(api_response.success);
1800 let data = api_response.data.unwrap();
1801
1802 assert!(data.policies.is_some());
1804 }
1805
1806 #[actix_web::test]
1807 async fn test_update_relayer_unset_notification() {
1808 let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
1809 relayer.notification_id = Some("test-notification".to_string());
1810 let app_state =
1811 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1812
1813 let patch = serde_json::json!({
1814 "notification_id": null
1815 });
1816
1817 let result = update_relayer(
1818 "test-relayer".to_string(),
1819 patch,
1820 actix_web::web::ThinData(app_state),
1821 )
1822 .await;
1823
1824 assert!(result.is_ok());
1825 let response = result.unwrap();
1826 assert_eq!(response.status(), 200);
1827
1828 let body = to_bytes(response.into_body()).await.unwrap();
1829 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1830
1831 assert!(api_response.success);
1832 let data = api_response.data.unwrap();
1833 assert_eq!(data.notification_id, None);
1834 }
1835
1836 #[actix_web::test]
1837 async fn test_update_relayer_unset_custom_rpc_urls() {
1838 let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
1839 relayer.custom_rpc_urls = Some(vec![crate::models::RpcConfig {
1840 url: "https://custom-rpc.example.com".to_string(),
1841 weight: 50,
1842 ..Default::default()
1843 }]);
1844 let app_state =
1845 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1846
1847 let patch = serde_json::json!({
1848 "custom_rpc_urls": null
1849 });
1850
1851 let result = update_relayer(
1852 "test-relayer".to_string(),
1853 patch,
1854 actix_web::web::ThinData(app_state),
1855 )
1856 .await;
1857
1858 assert!(result.is_ok());
1859 let response = result.unwrap();
1860 assert_eq!(response.status(), 200);
1861
1862 let body = to_bytes(response.into_body()).await.unwrap();
1863 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1864
1865 assert!(api_response.success);
1866 let data = api_response.data.unwrap();
1867 assert_eq!(data.custom_rpc_urls, None);
1868 }
1869
1870 #[actix_web::test]
1871 async fn test_update_relayer_set_custom_rpc_urls() {
1872 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1873 let app_state =
1874 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1875
1876 let patch = serde_json::json!({
1877 "custom_rpc_urls": [
1878 {
1879 "url": "https://rpc1.example.com",
1880 "weight": 80
1881 },
1882 {
1883 "url": "https://rpc2.example.com",
1884 "weight": 60
1885 }
1886 ]
1887 });
1888
1889 let result = update_relayer(
1890 "test-relayer".to_string(),
1891 patch,
1892 actix_web::web::ThinData(app_state),
1893 )
1894 .await;
1895
1896 assert!(result.is_ok());
1897 let response = result.unwrap();
1898 assert_eq!(response.status(), 200);
1899
1900 let body = to_bytes(response.into_body()).await.unwrap();
1901 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1902
1903 assert!(api_response.success);
1904 let data = api_response.data.unwrap();
1905
1906 assert!(data.custom_rpc_urls.is_some());
1907 let rpc_urls = data.custom_rpc_urls.unwrap();
1908 assert_eq!(rpc_urls.len(), 2);
1909 assert_eq!(rpc_urls[0].url, "https://rpc1.example.com");
1910 assert_eq!(rpc_urls[0].weight, 80);
1911 assert_eq!(rpc_urls[1].url, "https://rpc2.example.com");
1912 assert_eq!(rpc_urls[1].weight, 60);
1913 }
1914
1915 #[actix_web::test]
1916 async fn test_update_relayer_clear_policies() {
1917 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1918 let app_state =
1919 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1920
1921 let patch = serde_json::json!({
1922 "policies": null
1923 });
1924
1925 let result = update_relayer(
1926 "test-relayer".to_string(),
1927 patch,
1928 actix_web::web::ThinData(app_state),
1929 )
1930 .await;
1931
1932 assert!(result.is_ok());
1933 let response = result.unwrap();
1934 assert_eq!(response.status(), 200);
1935
1936 let body = to_bytes(response.into_body()).await.unwrap();
1937 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1938
1939 assert!(api_response.success);
1940 let data = api_response.data.unwrap();
1941 assert_eq!(data.policies, None);
1942 }
1943
1944 #[actix_web::test]
1945 async fn test_update_relayer_invalid_policy_structure() {
1946 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1947 let app_state =
1948 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1949
1950 let patch = serde_json::json!({
1951 "policies": {
1952 "invalid_field_name": "some_value"
1953 }
1954 });
1955
1956 let result = update_relayer(
1957 "test-relayer".to_string(),
1958 patch,
1959 actix_web::web::ThinData(app_state),
1960 )
1961 .await;
1962
1963 assert!(result.is_err());
1964 if let Err(ApiError::BadRequest(msg)) = result {
1965 assert!(msg.contains("Invalid policy"));
1966 } else {
1967 panic!("Expected BadRequest error for invalid policy structure");
1968 }
1969 }
1970
1971 #[actix_web::test]
1972 async fn test_update_relayer_invalid_evm_policy_values() {
1973 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1974 let app_state =
1975 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1976
1977 let patch = serde_json::json!({
1978 "policies": {
1979 "gas_price_cap": "invalid_number",
1980 "min_balance": -1
1981 }
1982 });
1983
1984 let result = update_relayer(
1985 "test-relayer".to_string(),
1986 patch,
1987 actix_web::web::ThinData(app_state),
1988 )
1989 .await;
1990
1991 assert!(result.is_err());
1992 if let Err(ApiError::BadRequest(msg)) = result {
1993 assert!(msg.contains("Invalid policy") || msg.contains("Invalid update request"));
1994 } else {
1995 panic!("Expected BadRequest error for invalid policy values");
1996 }
1997 }
1998
1999 #[actix_web::test]
2000 async fn test_update_relayer_multiple_fields_at_once() {
2001 let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
2002 relayer.notification_id = Some("old-notification".to_string());
2003 let app_state =
2004 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
2005
2006 let patch = serde_json::json!({
2007 "name": "Multi-Update Relayer",
2008 "paused": true,
2009 "notification_id": null,
2010 "policies": {
2011 "gas_price_cap": 40000000000u64,
2012 "eip1559_pricing": true
2013 },
2014 "custom_rpc_urls": [
2015 {
2016 "url": "https://new-rpc.example.com",
2017 "weight": 90
2018 }
2019 ]
2020 });
2021
2022 let result = update_relayer(
2023 "test-relayer".to_string(),
2024 patch,
2025 actix_web::web::ThinData(app_state),
2026 )
2027 .await;
2028
2029 assert!(result.is_ok());
2030 let response = result.unwrap();
2031 assert_eq!(response.status(), 200);
2032
2033 let body = to_bytes(response.into_body()).await.unwrap();
2034 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
2035
2036 assert!(api_response.success);
2037 let data = api_response.data.unwrap();
2038
2039 assert_eq!(data.name, "Multi-Update Relayer");
2041 assert!(data.paused);
2042 assert_eq!(data.notification_id, None);
2043
2044 assert!(data.policies.is_some());
2046 assert!(data.custom_rpc_urls.is_some());
2047 let rpc_urls = data.custom_rpc_urls.unwrap();
2048 assert_eq!(rpc_urls.len(), 1);
2049 assert_eq!(rpc_urls[0].url, "https://new-rpc.example.com");
2050 assert_eq!(rpc_urls[0].weight, 90);
2051 }
2052
2053 #[actix_web::test]
2054 async fn test_update_relayer_solana_policies() {
2055 use crate::models::{
2056 NetworkType, RelayerNetworkPolicy, RelayerSolanaPolicy, SolanaFeePaymentStrategy,
2057 };
2058
2059 let mut solana_relayer = create_mock_relayer("test-solana-relayer".to_string(), false);
2061 solana_relayer.network_type = NetworkType::Solana;
2062 solana_relayer.policies = RelayerNetworkPolicy::Solana(RelayerSolanaPolicy::default());
2063
2064 let app_state =
2065 create_mock_app_state(None, Some(vec![solana_relayer]), None, None, None, None).await;
2066
2067 let patch = serde_json::json!({
2068 "policies": {
2069 "fee_payment_strategy": "user",
2070 "min_balance": 2000000,
2071 "max_signatures": 5,
2072 "max_tx_data_size": 800,
2073 "max_allowed_fee_lamports": 25000,
2074 "fee_margin_percentage": 15.0
2075 }
2076 });
2077
2078 let result = update_relayer(
2079 "test-solana-relayer".to_string(),
2080 patch,
2081 actix_web::web::ThinData(app_state),
2082 )
2083 .await;
2084
2085 assert!(result.is_ok());
2086 let response = result.unwrap();
2087 assert_eq!(response.status(), 200);
2088
2089 let body = to_bytes(response.into_body()).await.unwrap();
2090 let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
2091
2092 assert!(api_response.success);
2093 let data = api_response.data.unwrap();
2094
2095 assert!(data.policies.is_some());
2097 let policies = data.policies.unwrap();
2098 if let RelayerNetworkPolicyResponse::Solana(solana_policy) = policies {
2099 assert_eq!(
2100 solana_policy.fee_payment_strategy,
2101 Some(SolanaFeePaymentStrategy::User)
2102 );
2103 assert_eq!(solana_policy.min_balance, 2000000);
2104 assert_eq!(solana_policy.max_signatures, Some(5));
2105 assert_eq!(solana_policy.max_tx_data_size, 800);
2106 assert_eq!(solana_policy.max_allowed_fee_lamports, Some(25000));
2107 assert_eq!(solana_policy.fee_margin_percentage, Some(15.0));
2108 } else {
2109 panic!("Expected Solana policies in response");
2110 }
2111 }
2112
2113 #[actix_web::test]
2116 async fn test_delete_relayer_success() {
2117 let relayer = create_mock_relayer("test-relayer".to_string(), false);
2118 let app_state =
2119 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
2120
2121 let result = delete_relayer(
2122 "test-relayer".to_string(),
2123 actix_web::web::ThinData(app_state),
2124 )
2125 .await;
2126
2127 assert!(result.is_ok());
2128 let response = result.unwrap();
2129 assert_eq!(response.status(), 200);
2130
2131 let body = to_bytes(response.into_body()).await.unwrap();
2132 let api_response: ApiResponse<String> = serde_json::from_slice(&body).unwrap();
2133
2134 assert!(api_response.success);
2135 let data = api_response.data.unwrap();
2136 assert!(data.contains("Relayer deleted successfully"));
2137 }
2138
2139 #[actix_web::test]
2140 async fn test_delete_relayer_with_transactions() {
2141 let relayer = create_mock_relayer("relayer-with-tx".to_string(), false);
2142 let mut transaction = create_mock_transaction();
2143 transaction.id = "test-tx".to_string();
2144 transaction.relayer_id = "relayer-with-tx".to_string();
2145 let app_state = create_mock_app_state(
2146 None,
2147 Some(vec![relayer]),
2148 None,
2149 None,
2150 None,
2151 Some(vec![transaction]),
2152 )
2153 .await;
2154
2155 let result = delete_relayer(
2156 "relayer-with-tx".to_string(),
2157 actix_web::web::ThinData(app_state),
2158 )
2159 .await;
2160
2161 assert!(result.is_err());
2162 if let Err(ApiError::BadRequest(msg)) = result {
2163 assert!(msg.contains("Cannot delete relayer 'relayer-with-tx'"));
2164 assert!(msg.contains("has 1 transaction(s)"));
2165 assert!(msg.contains("wait for all transactions to complete"));
2166 } else {
2167 panic!("Expected BadRequest error for relayer with transactions");
2168 }
2169 }
2170
2171 #[actix_web::test]
2172 async fn test_delete_relayer_nonexistent() {
2173 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
2174
2175 let result = delete_relayer(
2176 "nonexistent-relayer".to_string(),
2177 actix_web::web::ThinData(app_state),
2178 )
2179 .await;
2180
2181 assert!(result.is_err());
2182 if let Err(ApiError::NotFound(msg)) = result {
2183 assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
2184 } else {
2185 panic!("Expected NotFound error for nonexistent relayer");
2186 }
2187 }
2188
2189 #[actix_web::test]
2190 async fn test_sign_transaction_success() {
2191 let _lock = ENV_MUTEX.lock().await;
2192 setup_test_env();
2193 let network = create_mock_stellar_network();
2194 let signer = create_mock_signer();
2195 let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
2196 relayer.network_type = NetworkType::Stellar;
2197 let app_state = create_mock_app_state(
2198 None,
2199 Some(vec![relayer]),
2200 Some(vec![signer]),
2201 Some(vec![network]),
2202 None,
2203 None,
2204 )
2205 .await;
2206
2207 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2208 unsigned_xdr: "test-unsigned-xdr".to_string(),
2209 });
2210
2211 let result = sign_transaction(
2212 "test-relayer".to_string(),
2213 request,
2214 actix_web::web::ThinData(app_state),
2215 )
2216 .await;
2217
2218 assert!(result.is_err());
2221 cleanup_test_env();
2222 }
2223
2224 #[actix_web::test]
2225 async fn test_sign_transaction_relayer_not_found() {
2226 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
2227
2228 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2229 unsigned_xdr: "test-unsigned-xdr".to_string(),
2230 });
2231
2232 let result = sign_transaction(
2233 "nonexistent-relayer".to_string(),
2234 request,
2235 actix_web::web::ThinData(app_state),
2236 )
2237 .await;
2238
2239 assert!(result.is_err());
2240 if let Err(ApiError::NotFound(msg)) = result {
2241 assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
2242 } else {
2243 panic!("Expected NotFound error for nonexistent relayer");
2244 }
2245 }
2246
2247 #[actix_web::test]
2248 async fn test_sign_transaction_relayer_disabled() {
2249 let mut relayer = create_mock_relayer("disabled-relayer".to_string(), false);
2250 relayer.paused = true;
2251 let app_state =
2252 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
2253
2254 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2255 unsigned_xdr: "test-unsigned-xdr".to_string(),
2256 });
2257
2258 let result = sign_transaction(
2259 "disabled-relayer".to_string(),
2260 request,
2261 actix_web::web::ThinData(app_state),
2262 )
2263 .await;
2264
2265 assert!(result.is_err());
2266 if let Err(ApiError::ForbiddenError(msg)) = result {
2267 assert!(msg.contains("Relayer paused"));
2268 } else {
2269 panic!("Expected ForbiddenError for paused relayer");
2270 }
2271 }
2272
2273 #[actix_web::test]
2274 async fn test_sign_transaction_system_disabled() {
2275 let mut relayer = create_mock_relayer("system-disabled-relayer".to_string(), false);
2276 relayer.system_disabled = true;
2277 let app_state =
2278 create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
2279
2280 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2281 unsigned_xdr: "test-unsigned-xdr".to_string(),
2282 });
2283
2284 let result = sign_transaction(
2285 "system-disabled-relayer".to_string(),
2286 request,
2287 actix_web::web::ThinData(app_state),
2288 )
2289 .await;
2290
2291 assert!(result.is_err());
2292 if let Err(ApiError::ForbiddenError(msg)) = result {
2293 assert!(msg.contains("Relayer disabled"));
2294 } else {
2295 panic!("Expected ForbiddenError for system disabled relayer");
2296 }
2297 }
2298
2299 #[actix_web::test]
2300 async fn test_quote_sponsored_transaction_relayer_not_found() {
2301 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
2302
2303 let request = SponsoredTransactionQuoteRequest::Stellar(
2304 crate::models::StellarFeeEstimateRequestParams {
2305 transaction_xdr: Some("test-xdr".to_string()),
2306 operations: None,
2307 source_account: None,
2308 fee_token: "native".to_string(),
2309 },
2310 );
2311
2312 let result = quote_sponsored_transaction(
2313 "nonexistent-relayer".to_string(),
2314 request,
2315 actix_web::web::ThinData(app_state),
2316 )
2317 .await;
2318
2319 assert!(result.is_err());
2320 if let Err(ApiError::NotFound(msg)) = result {
2321 assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
2322 } else {
2323 panic!("Expected NotFound error for nonexistent relayer");
2324 }
2325 }
2326
2327 #[actix_web::test]
2328 async fn test_quote_sponsored_transaction_relayer_disabled() {
2329 let mut relayer = create_mock_relayer("disabled-relayer".to_string(), false);
2330 relayer.paused = true;
2331 relayer.network_type = NetworkType::Stellar;
2332 relayer.policies = crate::models::RelayerNetworkPolicy::Stellar(RelayerStellarPolicy {
2333 fee_payment_strategy: Some(StellarFeePaymentStrategy::User),
2334 ..Default::default()
2335 });
2336 let network = create_mock_stellar_network();
2337 let signer = create_mock_signer();
2338 let app_state = create_mock_app_state(
2339 None,
2340 Some(vec![relayer]),
2341 Some(vec![signer]),
2342 Some(vec![network]),
2343 None,
2344 None,
2345 )
2346 .await;
2347
2348 let request = SponsoredTransactionQuoteRequest::Stellar(
2349 crate::models::StellarFeeEstimateRequestParams {
2350 transaction_xdr: Some("test-xdr".to_string()),
2351 operations: None,
2352 source_account: None,
2353 fee_token: "native".to_string(),
2354 },
2355 );
2356
2357 let result = quote_sponsored_transaction(
2358 "disabled-relayer".to_string(),
2359 request,
2360 actix_web::web::ThinData(app_state),
2361 )
2362 .await;
2363
2364 assert!(result.is_err());
2365 if let Err(ApiError::ForbiddenError(msg)) = result {
2366 assert!(msg.contains("Relayer paused"));
2367 } else {
2368 panic!("Expected ForbiddenError for paused relayer");
2369 }
2370 }
2371
2372 #[actix_web::test]
2373 async fn test_build_sponsored_transaction_relayer_not_found() {
2374 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
2375
2376 let request = SponsoredTransactionBuildRequest::Stellar(
2377 crate::models::StellarPrepareTransactionRequestParams {
2378 transaction_xdr: Some("test-xdr".to_string()),
2379 operations: None,
2380 source_account: None,
2381 fee_token: "native".to_string(),
2382 },
2383 );
2384
2385 let result = build_sponsored_transaction(
2386 "nonexistent-relayer".to_string(),
2387 request,
2388 actix_web::web::ThinData(app_state),
2389 )
2390 .await;
2391
2392 assert!(result.is_err());
2393 if let Err(ApiError::NotFound(msg)) = result {
2394 assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
2395 } else {
2396 panic!("Expected NotFound error for nonexistent relayer");
2397 }
2398 }
2399
2400 #[actix_web::test]
2401 async fn test_build_sponsored_transaction_relayer_disabled() {
2402 let mut relayer = create_mock_relayer("disabled-relayer".to_string(), false);
2403 relayer.paused = true;
2404 relayer.network_type = NetworkType::Stellar;
2405 relayer.policies = crate::models::RelayerNetworkPolicy::Stellar(RelayerStellarPolicy {
2406 fee_payment_strategy: Some(StellarFeePaymentStrategy::User),
2407 ..Default::default()
2408 });
2409 let network = create_mock_stellar_network();
2410 let signer = create_mock_signer();
2411 let app_state = create_mock_app_state(
2412 None,
2413 Some(vec![relayer]),
2414 Some(vec![signer]),
2415 Some(vec![network]),
2416 None,
2417 None,
2418 )
2419 .await;
2420
2421 let request = SponsoredTransactionBuildRequest::Stellar(
2422 crate::models::StellarPrepareTransactionRequestParams {
2423 transaction_xdr: Some("test-xdr".to_string()),
2424 operations: None,
2425 source_account: None,
2426 fee_token: "native".to_string(),
2427 },
2428 );
2429
2430 let result = build_sponsored_transaction(
2431 "disabled-relayer".to_string(),
2432 request,
2433 actix_web::web::ThinData(app_state),
2434 )
2435 .await;
2436
2437 assert!(result.is_err());
2438 if let Err(ApiError::ForbiddenError(msg)) = result {
2439 assert!(msg.contains("Relayer paused"));
2440 } else {
2441 panic!("Expected ForbiddenError for paused relayer");
2442 }
2443 }
2444}