openzeppelin_relayer/api/controllers/
relayer.rs

1//! # Relayer Controller
2//!
3//! Handles HTTP endpoints for relayer operations including:
4//! - Listing relayers
5//! - Getting relayer details
6//! - Creating relayers
7//! - Updating relayers
8//! - Deleting relayers
9//! - Submitting transactions
10//! - Signing messages
11//! - JSON-RPC proxy
12use 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
40/// Lists all relayers with pagination support.
41///
42/// # Arguments
43///
44/// * `query` - The pagination query parameters.
45/// * `state` - The application state containing the relayer repository.
46///
47/// # Returns
48///
49/// A paginated list of relayers.
50pub 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
80/// Retrieves details of a specific relayer by its ID.
81///
82/// # Arguments
83///
84/// * `relayer_id` - The ID of the relayer to retrieve.
85/// * `state` - The application state containing the relayer repository.
86///
87/// # Returns
88///
89/// The details of the specified relayer.
90pub 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
112/// Creates a new relayer.
113///
114/// # Arguments
115///
116/// * `request` - The relayer creation request.
117/// * `state` - The application state containing the relayer repository.
118///
119/// # Returns
120///
121/// The created relayer or an error if creation fails.
122///
123/// # Validation
124///
125/// This endpoint performs comprehensive dependency validation before creating the relayer:
126/// - **Signer Validation**: Ensures the specified signer exists in the system
127/// - **Signer Uniqueness**: Validates that the signer is not already in use by another relayer on the same network
128/// - **Notification Validation**: If a notification ID is provided, validates it exists
129/// - **Network Validation**: Confirms the specified network exists for the given network type
130///
131/// All validations must pass before the relayer is created, ensuring referential integrity and security constraints.
132pub 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    // Convert request to domain relayer (validates automatically)
148    let relayer = RelayerDomainModel::try_from(request)?;
149
150    // Check if signer exists
151    let signer_model = state
152        .signer_repository
153        .get_by_id(relayer.signer_id.clone())
154        .await?;
155
156    // Check if network exists for the given network type
157    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    // Check if signer is already in use by another relayer on the same network
171    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    // Check if notification exists (if provided)
183    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    // Convert domain model to repository model
191    let mut relayer_model = RelayerRepoModel::from(relayer);
192
193    // get address from signer and set it to relayer model
194    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
217/// Updates a relayer's information.
218///
219/// # Arguments
220///
221/// * `relayer_id` - The ID of the relayer to update.
222/// * `update_req` - The update request containing new relayer data.
223/// * `state` - The application state containing the relayer repository.
224///
225/// # Returns
226///
227/// The updated relayer information.
228pub 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    // convert patch to UpdateRelayerRequest to validate
247    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    // Check if notification exists (if setting one) by extracting from JSON patch
260    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    // Apply JSON merge patch directly to domain object
268    let updated_domain = RelayerDomainModel::from(relayer.clone())
269        .apply_json_patch(&patch)
270        .map_err(ApiError::from)?;
271
272    // Use existing RelayerRepoUpdater to preserve runtime fields
273    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
285/// Deletes a relayer by ID.
286///
287/// # Arguments
288///
289/// * `relayer_id` - The ID of the relayer to delete.
290/// * `state` - The application state containing the relayer repository.
291///
292/// # Returns
293///
294/// A success response or an error if deletion fails.
295///
296/// # Security
297///
298/// This endpoint ensures that relayers cannot be deleted if they have any pending
299/// or active transactions. This prevents data loss and maintains system integrity.
300pub 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    // Check if the relayer exists
316    let _relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
317
318    // Check if the relayer has any active transactions (pending or otherwise)
319    // Use optimized count_by_status
320    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    // Safe to delete - no transactions associated with this relayer
339    state.relayer_repository.delete_by_id(relayer_id).await?;
340
341    Ok(HttpResponse::Ok().json(ApiResponse::success("Relayer deleted successfully")))
342}
343
344/// Retrieves the status of a specific relayer.
345///
346/// # Arguments
347///
348/// * `relayer_id` - The ID of the relayer to check status for.
349/// * `state` - The application state containing the relayer repository.
350///
351/// # Returns
352///
353/// The status of the specified relayer.
354pub 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
376/// Retrieves the balance of a specific relayer.
377///
378/// # Arguments
379///
380/// * `relayer_id` - The ID of the relayer to check balance for.
381/// * `state` - The application state containing the relayer repository.
382///
383/// # Returns
384///
385/// The balance of the specified relayer.
386pub 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
408/// Sends a transaction through a specified relayer.
409///
410/// # Arguments
411///
412/// * `relayer_id` - The ID of the relayer to send the transaction through.
413/// * `request` - The transaction request data.
414/// * `state` - The application state containing the relayer repository.
415///
416/// # Returns
417///
418/// The response of the transaction processing.
419pub 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
441/// Retrieves a transaction by its ID for a specific relayer.
442///
443/// # Arguments
444///
445/// * `relayer_id` - The ID of the relayer.
446/// * `transaction_id` - The ID of the transaction to retrieve.
447/// * `state` - The application state containing the transaction repository.
448///
449/// # Returns
450///
451/// The details of the specified transaction.
452pub 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    // validation purpose only, checks if relayer exists
474    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
483/// Retrieves a transaction by its nonce for a specific relayer.
484///
485/// # Arguments
486///
487/// * `relayer_id` - The ID of the relayer.
488/// * `nonce` - The nonce of the transaction to retrieve.
489/// * `state` - The application state containing the transaction repository.
490///
491/// # Returns
492///
493/// The details of the specified transaction.
494pub 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    // get by nonce is only supported for EVM network
513    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
530/// Lists all transactions for a specific relayer with pagination support.
531///
532/// # Arguments
533///
534/// * `relayer_id` - The ID of the relayer.
535/// * `query` - The pagination query parameters.
536/// * `state` - The application state containing the transaction repository.
537///
538/// # Returns
539///
540/// A paginated list of transactions
541pub 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
577/// Deletes all pending transactions for a specific relayer.
578///
579/// # Arguments
580///
581/// * `relayer_id` - The ID of the relayer.
582/// * `state` - The application state containing the relayer repository.
583///
584/// # Returns
585///
586/// A success response with details about cancelled and failed transactions.
587pub 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
611/// Cancels a specific transaction for a relayer.
612///
613/// # Arguments
614///
615/// * `relayer_id` - The ID of the relayer.
616/// * `transaction_id` - The ID of the transaction to cancel.
617/// * `state` - The application state containing the transaction repository.
618///
619/// # Returns
620///
621/// The details of the canceled transaction.
622pub 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
643/// Replaces a specific transaction for a relayer.
644///
645/// # Arguments
646///
647/// * `relayer_id` - The ID of the relayer.
648/// * `transaction_id` - The ID of the transaction to replace.
649/// * `request` - The new transaction request data.
650/// * `state` - The application state containing the transaction repository.
651///
652/// # Returns
653///
654/// The details of the replaced transaction.
655pub 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
683/// Signs data using a specific relayer.
684///
685/// # Arguments
686///
687/// * `relayer_id` - The ID of the relayer.
688/// * `request` - The sign data request.
689/// * `state` - The application state containing the relayer repository.
690///
691/// # Returns
692///
693/// The signed data response.
694pub 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
723/// Signs typed data using a specific relayer.
724///
725/// # Arguments
726///
727/// * `relayer_id` - The ID of the relayer.
728/// * `request` - The sign typed data request.
729/// * `state` - The application state containing the relayer repository.
730///
731/// # Returns
732///
733/// The signed typed data response.
734pub 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
759/// Performs a JSON-RPC call through a specific relayer.
760///
761/// # Arguments
762///
763/// * `relayer_id` - The ID of the relayer.
764/// * `request` - The raw JSON-RPC request value.
765/// * `state` - The application state containing the relayer repository.
766///
767/// # Returns
768///
769/// The result of the JSON-RPC call.
770pub 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
796/// Signs a transaction using a specific relayer
797///
798/// # Arguments
799///
800/// * `relayer_id` - The ID of the relayer.
801/// * `request` - The sign transaction request containing unsigned XDR.
802/// * `state` - The application state containing the relayer repository.
803///
804/// # Returns
805///
806/// The signed transaction response.
807pub 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    // Get the network relayer and use its sign_transaction method
827    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
833/// Estimates fees for a transaction using gas abstraction.
834///
835/// # Arguments
836///
837/// * `relayer_id` - The ID of the relayer.
838/// * `params` - The fee estimate request parameters.
839/// * `state` - The application state containing the relayer repository.
840///
841/// # Returns
842///
843/// The fee estimate result.
844pub 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
871/// Prepares a transaction with fee payments using gas abstraction.
872///
873/// # Arguments
874///
875/// * `relayer_id` - The ID of the relayer.
876/// * `params` - The prepare transaction request parameters (network-agnostic).
877/// * `state` - The application state containing the relayer repository.
878///
879/// # Returns
880///
881/// The prepare transaction result.
882pub 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"); // noboost nosemgrep
935        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    /// Helper function to create a test relayer create request
944    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    /// Helper function to create a mock Solana network
965    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    /// Helper function to create a mock Stellar network
988    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    // CREATE RELAYER TESTS
1015
1016    #[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", // Using "test" to match the mock network name
1036            "test", // Using "test" to match the mock signer id
1037            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"); // This one keeps custom name from the request
1053        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", // Using "test" to match the mock network name
1077            "test", // Using "test" to match the mock signer id
1078            None,
1079        );
1080
1081        // Add EVM policies
1082        request.policies = Some(CreateRelayerPolicyRequest::Evm(RelayerEvmPolicy {
1083            gas_price_cap: Some(50000000000),
1084            min_balance: Some(1000000000000000000),
1085            eip1559_pricing: Some(true),
1086            private_transactions: Some(false),
1087            gas_limit_estimation: Some(true),
1088            whitelist_receivers: Some(vec![
1089                "0x1234567890123456789012345678901234567890".to_string()
1090            ]),
1091        }));
1092
1093        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1094
1095        assert!(result.is_ok());
1096        let response = result.unwrap();
1097        assert_eq!(response.status(), 201);
1098
1099        let body = to_bytes(response.into_body()).await.unwrap();
1100        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1101
1102        assert!(api_response.success);
1103        let data = api_response.data.unwrap();
1104        assert_eq!(data.id, "test-relayer-policies");
1105        assert_eq!(data.name, "Test Relayer with Policies");
1106        assert_eq!(data.network, "test");
1107
1108        // Verify policies are present in response
1109        assert!(data.policies.is_some());
1110        cleanup_test_env();
1111    }
1112
1113    #[actix_web::test]
1114    async fn test_create_relayer_with_partial_evm_policies() {
1115        let _lock = ENV_MUTEX.lock().await;
1116        setup_test_env();
1117        let network = create_mock_network();
1118        let signer = create_mock_signer();
1119        let app_state = create_mock_app_state(
1120            None,
1121            None,
1122            Some(vec![signer]),
1123            Some(vec![network]),
1124            None,
1125            None,
1126        )
1127        .await;
1128
1129        let mut request = create_test_relayer_create_request(
1130            Some("test-relayer-partial".to_string()),
1131            "Test Relayer with Partial Policies",
1132            "test",
1133            "test",
1134            None,
1135        );
1136
1137        // Add partial EVM policies
1138        request.policies = Some(CreateRelayerPolicyRequest::Evm(RelayerEvmPolicy {
1139            gas_price_cap: Some(30000000000),
1140            eip1559_pricing: Some(false),
1141            min_balance: None,
1142            private_transactions: None,
1143            gas_limit_estimation: None,
1144            whitelist_receivers: None,
1145        }));
1146
1147        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1148
1149        assert!(result.is_ok());
1150        let response = result.unwrap();
1151        assert_eq!(response.status(), 201);
1152
1153        let body = to_bytes(response.into_body()).await.unwrap();
1154        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1155
1156        assert!(api_response.success);
1157        let data = api_response.data.unwrap();
1158        assert_eq!(data.id, "test-relayer-partial");
1159
1160        // Verify partial policies are present in response
1161        assert!(data.policies.is_some());
1162        cleanup_test_env();
1163    }
1164
1165    #[actix_web::test]
1166    async fn test_create_relayer_with_solana_policies() {
1167        let _lock = ENV_MUTEX.lock().await;
1168        setup_test_env();
1169        let network = create_mock_solana_network();
1170        let signer = create_mock_signer();
1171        let app_state = create_mock_app_state(
1172            None,
1173            None,
1174            Some(vec![signer]),
1175            Some(vec![network]),
1176            None,
1177            None,
1178        )
1179        .await;
1180
1181        let mut request = create_test_relayer_create_request(
1182            Some("test-solana-relayer".to_string()),
1183            "Test Solana Relayer",
1184            "test",
1185            "test",
1186            None,
1187        );
1188
1189        // Change network type to Solana and add Solana policies
1190        request.network_type = RelayerNetworkType::Solana;
1191        request.policies = Some(CreateRelayerPolicyRequest::Solana(RelayerSolanaPolicy {
1192            fee_payment_strategy: Some(SolanaFeePaymentStrategy::Relayer),
1193            min_balance: Some(5000000),
1194            max_signatures: Some(10),
1195            max_tx_data_size: Some(1232),
1196            max_allowed_fee_lamports: Some(50000),
1197            allowed_programs: None, // Simplified to avoid validation issues
1198            allowed_tokens: None,
1199            fee_margin_percentage: Some(10.0),
1200            allowed_accounts: None,
1201            disallowed_accounts: None,
1202            swap_config: None,
1203        }));
1204
1205        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1206
1207        assert!(result.is_ok());
1208        let response = result.unwrap();
1209        assert_eq!(response.status(), 201);
1210
1211        let body = to_bytes(response.into_body()).await.unwrap();
1212        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1213
1214        assert!(api_response.success);
1215        let data = api_response.data.unwrap();
1216        assert_eq!(data.id, "test-solana-relayer");
1217        assert_eq!(data.network_type, RelayerNetworkType::Solana);
1218        assert_eq!(data.name, "Test Solana Relayer");
1219
1220        // Verify Solana policies are present in response
1221        assert!(data.policies.is_some());
1222        // verify policies are correct
1223        let policies = data.policies.unwrap();
1224        if let RelayerNetworkPolicyResponse::Solana(solana_policy) = policies {
1225            assert_eq!(
1226                solana_policy.fee_payment_strategy,
1227                Some(SolanaFeePaymentStrategy::Relayer)
1228            );
1229            assert_eq!(solana_policy.min_balance, 5000000);
1230            assert_eq!(solana_policy.max_signatures, Some(10));
1231            assert_eq!(solana_policy.max_tx_data_size, 1232);
1232            assert_eq!(solana_policy.max_allowed_fee_lamports, Some(50000));
1233        } else {
1234            panic!("Expected Solana policies");
1235        }
1236        cleanup_test_env();
1237    }
1238
1239    #[actix_web::test]
1240    async fn test_create_relayer_with_stellar_policies() {
1241        let _lock = ENV_MUTEX.lock().await;
1242        setup_test_env();
1243        let network = create_mock_stellar_network();
1244        let signer = create_mock_signer();
1245        let app_state = create_mock_app_state(
1246            None,
1247            None,
1248            Some(vec![signer]),
1249            Some(vec![network]),
1250            None,
1251            None,
1252        )
1253        .await;
1254
1255        let mut request = create_test_relayer_create_request(
1256            Some("test-stellar-relayer".to_string()),
1257            "Test Stellar Relayer",
1258            "test",
1259            "test",
1260            None,
1261        );
1262
1263        // Change network type to Stellar and add Stellar policies
1264        request.network_type = RelayerNetworkType::Stellar;
1265        request.policies = Some(CreateRelayerPolicyRequest::Stellar(RelayerStellarPolicy {
1266            min_balance: Some(10000000),
1267            max_fee: Some(100),
1268            timeout_seconds: Some(30),
1269            concurrent_transactions: None,
1270            allowed_tokens: None,
1271            fee_payment_strategy: Some(StellarFeePaymentStrategy::Relayer),
1272            slippage_percentage: None,
1273            fee_margin_percentage: None,
1274            swap_config: None,
1275        }));
1276
1277        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1278
1279        assert!(result.is_ok());
1280        let response = result.unwrap();
1281        assert_eq!(response.status(), 201);
1282
1283        let body = to_bytes(response.into_body()).await.unwrap();
1284        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1285
1286        assert!(api_response.success);
1287        let data = api_response.data.unwrap();
1288        assert_eq!(data.id, "test-stellar-relayer");
1289        assert_eq!(data.network_type, RelayerNetworkType::Stellar);
1290
1291        // Verify Stellar policies are present in response
1292        assert!(data.policies.is_some());
1293        cleanup_test_env();
1294    }
1295
1296    #[actix_web::test]
1297    async fn test_create_relayer_with_policy_type_mismatch() {
1298        let _lock = ENV_MUTEX.lock().await;
1299        setup_test_env();
1300        let network = create_mock_network();
1301        let signer = create_mock_signer();
1302        let app_state = create_mock_app_state(
1303            None,
1304            None,
1305            Some(vec![signer]),
1306            Some(vec![network]),
1307            None,
1308            None,
1309        )
1310        .await;
1311
1312        let mut request = create_test_relayer_create_request(
1313            Some("test-mismatch-relayer".to_string()),
1314            "Test Mismatch Relayer",
1315            "test",
1316            "test",
1317            None,
1318        );
1319
1320        // Set network type to EVM but provide Solana policies (should fail)
1321        request.network_type = RelayerNetworkType::Evm;
1322        request.policies = Some(CreateRelayerPolicyRequest::Solana(
1323            RelayerSolanaPolicy::default(),
1324        ));
1325
1326        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1327
1328        assert!(result.is_err());
1329        if let Err(ApiError::BadRequest(msg)) = result {
1330            assert!(msg.contains("Policy type does not match relayer network type"));
1331        } else {
1332            panic!("Expected BadRequest error for policy type mismatch");
1333        }
1334        cleanup_test_env();
1335    }
1336
1337    #[actix_web::test]
1338    async fn test_create_relayer_with_notification() {
1339        let _lock = ENV_MUTEX.lock().await;
1340        setup_test_env();
1341        let network = create_mock_network();
1342        let signer = create_mock_signer();
1343        let notification = create_mock_notification("test-notification".to_string());
1344        let app_state = create_mock_app_state(
1345            None,
1346            None,
1347            Some(vec![signer]),
1348            Some(vec![network]),
1349            None,
1350            None,
1351        )
1352        .await;
1353
1354        // Add notification manually since create_mock_app_state doesn't handle notifications
1355        app_state
1356            .notification_repository
1357            .create(notification)
1358            .await
1359            .unwrap();
1360
1361        let request = create_test_relayer_create_request(
1362            Some("test-relayer".to_string()),
1363            "Test Relayer",
1364            "test", // Using "test" to match the mock network name
1365            "test", // Using "test" to match the mock signer id
1366            Some("test-notification".to_string()),
1367        );
1368
1369        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1370
1371        assert!(result.is_ok());
1372        let response = result.unwrap();
1373        assert_eq!(response.status(), 201);
1374        let body = to_bytes(response.into_body()).await.unwrap();
1375        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1376
1377        assert!(api_response.success);
1378        let data = api_response.data.unwrap();
1379        assert_eq!(data.notification_id, Some("test-notification".to_string()));
1380        cleanup_test_env();
1381    }
1382
1383    #[actix_web::test]
1384    async fn test_create_relayer_nonexistent_signer() {
1385        let network = create_mock_network();
1386        let app_state =
1387            create_mock_app_state(None, None, None, Some(vec![network]), None, None).await;
1388
1389        let request = create_test_relayer_create_request(
1390            Some("test-relayer".to_string()),
1391            "Test Relayer",
1392            "test", // Using "test" to match the mock network name
1393            "nonexistent-signer",
1394            None,
1395        );
1396
1397        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1398
1399        assert!(result.is_err());
1400        if let Err(ApiError::NotFound(msg)) = result {
1401            assert!(msg.contains("Signer with ID nonexistent-signer not found"));
1402        } else {
1403            panic!("Expected NotFound error for nonexistent signer");
1404        }
1405    }
1406
1407    #[actix_web::test]
1408    async fn test_create_relayer_nonexistent_network() {
1409        let signer = create_mock_signer();
1410        let app_state =
1411            create_mock_app_state(None, None, Some(vec![signer]), None, None, None).await;
1412
1413        let request = create_test_relayer_create_request(
1414            Some("test-relayer".to_string()),
1415            "Test Relayer",
1416            "nonexistent-network",
1417            "test", // Using "test" to match the mock signer id
1418            None,
1419        );
1420
1421        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1422
1423        assert!(result.is_err());
1424        if let Err(ApiError::BadRequest(msg)) = result {
1425            assert!(msg.contains("Network 'nonexistent-network' not found"));
1426            assert!(msg.contains("network configuration exists"));
1427        } else {
1428            panic!("Expected BadRequest error for nonexistent network");
1429        }
1430    }
1431
1432    #[actix_web::test]
1433    async fn test_create_relayer_signer_already_in_use() {
1434        let network = create_mock_network();
1435        let signer = create_mock_signer();
1436        let mut existing_relayer = create_mock_relayer("existing-relayer".to_string(), false);
1437        existing_relayer.signer_id = "test".to_string(); // Match the mock signer id
1438        existing_relayer.network = "test".to_string(); // Match the mock network name
1439        let app_state = create_mock_app_state(
1440            None,
1441            Some(vec![existing_relayer]),
1442            Some(vec![signer]),
1443            Some(vec![network]),
1444            None,
1445            None,
1446        )
1447        .await;
1448
1449        let request = create_test_relayer_create_request(
1450            Some("test-relayer".to_string()),
1451            "Test Relayer",
1452            "test", // Using "test" to match the mock network name
1453            "test", // Using "test" to match the mock signer id
1454            None,
1455        );
1456
1457        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1458
1459        assert!(result.is_err());
1460        if let Err(ApiError::BadRequest(msg)) = result {
1461            assert!(msg.contains("signer 'test' is already in use"));
1462            assert!(msg.contains("relayer 'existing-relayer'"));
1463            assert!(msg.contains("network 'test'"));
1464            assert!(msg.contains("security reasons"));
1465        } else {
1466            panic!("Expected BadRequest error for signer already in use");
1467        }
1468    }
1469
1470    #[actix_web::test]
1471    async fn test_create_relayer_nonexistent_notification() {
1472        let network = create_mock_network();
1473        let signer = create_mock_signer();
1474        let app_state = create_mock_app_state(
1475            None,
1476            None,
1477            Some(vec![signer]),
1478            Some(vec![network]),
1479            None,
1480            None,
1481        )
1482        .await;
1483
1484        let request = create_test_relayer_create_request(
1485            Some("test-relayer".to_string()),
1486            "Test Relayer",
1487            "test", // Using "test" to match the mock network name
1488            "test", // Using "test" to match the mock signer id
1489            Some("nonexistent-notification".to_string()),
1490        );
1491
1492        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1493
1494        assert!(result.is_err());
1495        if let Err(ApiError::NotFound(msg)) = result {
1496            assert!(msg.contains("Notification with ID 'nonexistent-notification' not found"));
1497        } else {
1498            panic!("Expected NotFound error for nonexistent notification");
1499        }
1500    }
1501
1502    // LIST RELAYERS TESTS
1503
1504    #[actix_web::test]
1505    async fn test_list_relayers_success() {
1506        let relayer1 = create_mock_relayer("relayer-1".to_string(), false);
1507        let relayer2 = create_mock_relayer("relayer-2".to_string(), false);
1508        let app_state =
1509            create_mock_app_state(None, Some(vec![relayer1, relayer2]), None, None, None, None)
1510                .await;
1511
1512        let query = PaginationQuery {
1513            page: 1,
1514            per_page: 10,
1515        };
1516
1517        let result = list_relayers(query, actix_web::web::ThinData(app_state)).await;
1518
1519        assert!(result.is_ok());
1520        let response = result.unwrap();
1521        assert_eq!(response.status(), 200);
1522
1523        let body = to_bytes(response.into_body()).await.unwrap();
1524        let api_response: ApiResponse<Vec<RelayerResponse>> =
1525            serde_json::from_slice(&body).unwrap();
1526
1527        assert!(api_response.success);
1528        let data = api_response.data.unwrap();
1529        assert_eq!(data.len(), 2);
1530    }
1531
1532    #[actix_web::test]
1533    async fn test_list_relayers_empty() {
1534        let app_state = create_mock_app_state(None, None, None, None, None, None).await;
1535
1536        let query = PaginationQuery {
1537            page: 1,
1538            per_page: 10,
1539        };
1540
1541        let result = list_relayers(query, actix_web::web::ThinData(app_state)).await;
1542
1543        assert!(result.is_ok());
1544        let response = result.unwrap();
1545        assert_eq!(response.status(), 200);
1546
1547        let body = to_bytes(response.into_body()).await.unwrap();
1548        let api_response: ApiResponse<Vec<RelayerResponse>> =
1549            serde_json::from_slice(&body).unwrap();
1550
1551        assert!(api_response.success);
1552        let data = api_response.data.unwrap();
1553        assert_eq!(data.len(), 0);
1554    }
1555
1556    // GET RELAYER TESTS
1557
1558    #[actix_web::test]
1559    async fn test_get_relayer_success() {
1560        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1561        let app_state =
1562            create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1563
1564        let result = get_relayer(
1565            "test-relayer".to_string(),
1566            actix_web::web::ThinData(app_state),
1567        )
1568        .await;
1569
1570        assert!(result.is_ok());
1571        let response = result.unwrap();
1572        assert_eq!(response.status(), 200);
1573
1574        let body = to_bytes(response.into_body()).await.unwrap();
1575        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1576
1577        assert!(api_response.success);
1578        let data = api_response.data.unwrap();
1579        assert_eq!(data.id, "test-relayer");
1580        assert_eq!(data.name, "Relayer test-relayer"); // Mock utility creates name as "Relayer {id}"
1581    }
1582
1583    #[actix_web::test]
1584    async fn test_get_relayer_not_found() {
1585        let app_state = create_mock_app_state(None, None, None, None, None, None).await;
1586
1587        let result = get_relayer(
1588            "nonexistent".to_string(),
1589            actix_web::web::ThinData(app_state),
1590        )
1591        .await;
1592
1593        assert!(result.is_err());
1594        if let Err(ApiError::NotFound(msg)) = result {
1595            assert!(msg.contains("Relayer with ID nonexistent not found"));
1596        } else {
1597            panic!("Expected NotFound error");
1598        }
1599    }
1600
1601    // UPDATE RELAYER TESTS
1602
1603    #[actix_web::test]
1604    async fn test_update_relayer_success() {
1605        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1606        let app_state =
1607            create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1608
1609        let patch = serde_json::json!({
1610            "name": "Updated Relayer Name",
1611            "paused": true
1612        });
1613
1614        let result = update_relayer(
1615            "test-relayer".to_string(),
1616            patch,
1617            actix_web::web::ThinData(app_state),
1618        )
1619        .await;
1620
1621        assert!(result.is_ok());
1622        let response = result.unwrap();
1623        assert_eq!(response.status(), 200);
1624
1625        let body = to_bytes(response.into_body()).await.unwrap();
1626        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1627
1628        assert!(api_response.success);
1629        let data = api_response.data.unwrap();
1630        assert_eq!(data.name, "Updated Relayer Name");
1631        assert!(data.paused);
1632    }
1633
1634    #[actix_web::test]
1635    async fn test_update_relayer_system_disabled() {
1636        let mut relayer = create_mock_relayer("disabled-relayer".to_string(), false);
1637        relayer.system_disabled = true;
1638        let app_state =
1639            create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1640
1641        let patch = serde_json::json!({
1642            "name": "Updated Name"
1643        });
1644
1645        let result = update_relayer(
1646            "disabled-relayer".to_string(),
1647            patch,
1648            actix_web::web::ThinData(app_state),
1649        )
1650        .await;
1651
1652        assert!(result.is_err());
1653        if let Err(ApiError::BadRequest(msg)) = result {
1654            assert!(msg.contains("Relayer is disabled"));
1655        } else {
1656            panic!("Expected BadRequest error for disabled relayer");
1657        }
1658    }
1659
1660    #[actix_web::test]
1661    async fn test_update_relayer_invalid_patch() {
1662        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1663        let app_state =
1664            create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1665
1666        let patch = serde_json::json!({
1667            "invalid_field": "value"
1668        });
1669
1670        let result = update_relayer(
1671            "test-relayer".to_string(),
1672            patch,
1673            actix_web::web::ThinData(app_state),
1674        )
1675        .await;
1676
1677        assert!(result.is_err());
1678        if let Err(ApiError::BadRequest(msg)) = result {
1679            assert!(msg.contains("Invalid update request"));
1680        } else {
1681            panic!("Expected BadRequest error for invalid patch");
1682        }
1683    }
1684
1685    #[actix_web::test]
1686    async fn test_update_relayer_nonexistent() {
1687        let app_state = create_mock_app_state(None, None, None, None, None, None).await;
1688
1689        let patch = serde_json::json!({
1690            "name": "Updated Name"
1691        });
1692
1693        let result = update_relayer(
1694            "nonexistent-relayer".to_string(),
1695            patch,
1696            actix_web::web::ThinData(app_state),
1697        )
1698        .await;
1699
1700        assert!(result.is_err());
1701        if let Err(ApiError::NotFound(msg)) = result {
1702            assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
1703        } else {
1704            panic!("Expected NotFound error for nonexistent relayer");
1705        }
1706    }
1707
1708    #[actix_web::test]
1709    async fn test_update_relayer_set_evm_policies() {
1710        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1711        let app_state =
1712            create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1713
1714        let patch = serde_json::json!({
1715            "policies": {
1716                "gas_price_cap": 50000000000u64,
1717                "min_balance": 1000000000000000000u64,
1718                "eip1559_pricing": true,
1719                "private_transactions": false,
1720                "gas_limit_estimation": true,
1721                "whitelist_receivers": ["0x1234567890123456789012345678901234567890"]
1722            }
1723        });
1724
1725        let result = update_relayer(
1726            "test-relayer".to_string(),
1727            patch,
1728            actix_web::web::ThinData(app_state),
1729        )
1730        .await;
1731
1732        assert!(result.is_ok());
1733        let response = result.unwrap();
1734        assert_eq!(response.status(), 200);
1735
1736        let body = to_bytes(response.into_body()).await.unwrap();
1737        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1738
1739        assert!(api_response.success);
1740        let data = api_response.data.unwrap();
1741
1742        // For now, just verify that the policies field exists
1743        // The policy validation can be added once we understand the correct structure
1744        assert!(data.policies.is_some());
1745    }
1746
1747    #[actix_web::test]
1748    async fn test_update_relayer_partial_policy_update() {
1749        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1750        let app_state =
1751            create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1752
1753        // First update with some policies
1754        let patch1 = serde_json::json!({
1755            "policies": {
1756                "gas_price_cap": 30000000000u64,
1757                "min_balance": 500000000000000000u64,
1758                "eip1559_pricing": false
1759            }
1760        });
1761
1762        let result1 = update_relayer(
1763            "test-relayer".to_string(),
1764            patch1,
1765            actix_web::web::ThinData(app_state),
1766        )
1767        .await;
1768
1769        assert!(result1.is_ok());
1770
1771        // Create fresh app state for second update test
1772        let relayer2 = create_mock_relayer("test-relayer".to_string(), false);
1773        let app_state2 =
1774            create_mock_app_state(None, Some(vec![relayer2]), None, None, None, None).await;
1775
1776        // Second update with only gas_price_cap change
1777        let patch2 = serde_json::json!({
1778            "policies": {
1779                "gas_price_cap": 60000000000u64
1780            }
1781        });
1782
1783        let result2 = update_relayer(
1784            "test-relayer".to_string(),
1785            patch2,
1786            actix_web::web::ThinData(app_state2),
1787        )
1788        .await;
1789
1790        assert!(result2.is_ok());
1791        let response = result2.unwrap();
1792        assert_eq!(response.status(), 200);
1793
1794        let body = to_bytes(response.into_body()).await.unwrap();
1795        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1796
1797        assert!(api_response.success);
1798        let data = api_response.data.unwrap();
1799
1800        // Just verify policies exist for now
1801        assert!(data.policies.is_some());
1802    }
1803
1804    #[actix_web::test]
1805    async fn test_update_relayer_unset_notification() {
1806        let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
1807        relayer.notification_id = Some("test-notification".to_string());
1808        let app_state =
1809            create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1810
1811        let patch = serde_json::json!({
1812            "notification_id": null
1813        });
1814
1815        let result = update_relayer(
1816            "test-relayer".to_string(),
1817            patch,
1818            actix_web::web::ThinData(app_state),
1819        )
1820        .await;
1821
1822        assert!(result.is_ok());
1823        let response = result.unwrap();
1824        assert_eq!(response.status(), 200);
1825
1826        let body = to_bytes(response.into_body()).await.unwrap();
1827        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1828
1829        assert!(api_response.success);
1830        let data = api_response.data.unwrap();
1831        assert_eq!(data.notification_id, None);
1832    }
1833
1834    #[actix_web::test]
1835    async fn test_update_relayer_unset_custom_rpc_urls() {
1836        let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
1837        relayer.custom_rpc_urls = Some(vec![crate::models::RpcConfig {
1838            url: "https://custom-rpc.example.com".to_string(),
1839            weight: 50,
1840            ..Default::default()
1841        }]);
1842        let app_state =
1843            create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1844
1845        let patch = serde_json::json!({
1846            "custom_rpc_urls": null
1847        });
1848
1849        let result = update_relayer(
1850            "test-relayer".to_string(),
1851            patch,
1852            actix_web::web::ThinData(app_state),
1853        )
1854        .await;
1855
1856        assert!(result.is_ok());
1857        let response = result.unwrap();
1858        assert_eq!(response.status(), 200);
1859
1860        let body = to_bytes(response.into_body()).await.unwrap();
1861        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1862
1863        assert!(api_response.success);
1864        let data = api_response.data.unwrap();
1865        assert_eq!(data.custom_rpc_urls, None);
1866    }
1867
1868    #[actix_web::test]
1869    async fn test_update_relayer_set_custom_rpc_urls() {
1870        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1871        let app_state =
1872            create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1873
1874        let patch = serde_json::json!({
1875            "custom_rpc_urls": [
1876                {
1877                    "url": "https://rpc1.example.com",
1878                    "weight": 80
1879                },
1880                {
1881                    "url": "https://rpc2.example.com",
1882                    "weight": 60
1883                }
1884            ]
1885        });
1886
1887        let result = update_relayer(
1888            "test-relayer".to_string(),
1889            patch,
1890            actix_web::web::ThinData(app_state),
1891        )
1892        .await;
1893
1894        assert!(result.is_ok());
1895        let response = result.unwrap();
1896        assert_eq!(response.status(), 200);
1897
1898        let body = to_bytes(response.into_body()).await.unwrap();
1899        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1900
1901        assert!(api_response.success);
1902        let data = api_response.data.unwrap();
1903
1904        assert!(data.custom_rpc_urls.is_some());
1905        let rpc_urls = data.custom_rpc_urls.unwrap();
1906        assert_eq!(rpc_urls.len(), 2);
1907        assert_eq!(rpc_urls[0].url, "https://rpc1.example.com");
1908        assert_eq!(rpc_urls[0].weight, 80);
1909        assert_eq!(rpc_urls[1].url, "https://rpc2.example.com");
1910        assert_eq!(rpc_urls[1].weight, 60);
1911    }
1912
1913    #[actix_web::test]
1914    async fn test_update_relayer_clear_policies() {
1915        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1916        let app_state =
1917            create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1918
1919        let patch = serde_json::json!({
1920            "policies": null
1921        });
1922
1923        let result = update_relayer(
1924            "test-relayer".to_string(),
1925            patch,
1926            actix_web::web::ThinData(app_state),
1927        )
1928        .await;
1929
1930        assert!(result.is_ok());
1931        let response = result.unwrap();
1932        assert_eq!(response.status(), 200);
1933
1934        let body = to_bytes(response.into_body()).await.unwrap();
1935        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1936
1937        assert!(api_response.success);
1938        let data = api_response.data.unwrap();
1939        assert_eq!(data.policies, None);
1940    }
1941
1942    #[actix_web::test]
1943    async fn test_update_relayer_invalid_policy_structure() {
1944        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1945        let app_state =
1946            create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1947
1948        let patch = serde_json::json!({
1949            "policies": {
1950                "invalid_field_name": "some_value"
1951            }
1952        });
1953
1954        let result = update_relayer(
1955            "test-relayer".to_string(),
1956            patch,
1957            actix_web::web::ThinData(app_state),
1958        )
1959        .await;
1960
1961        assert!(result.is_err());
1962        if let Err(ApiError::BadRequest(msg)) = result {
1963            assert!(msg.contains("Invalid policy"));
1964        } else {
1965            panic!("Expected BadRequest error for invalid policy structure");
1966        }
1967    }
1968
1969    #[actix_web::test]
1970    async fn test_update_relayer_invalid_evm_policy_values() {
1971        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1972        let app_state =
1973            create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
1974
1975        let patch = serde_json::json!({
1976            "policies": {
1977                "gas_price_cap": "invalid_number",
1978                "min_balance": -1
1979            }
1980        });
1981
1982        let result = update_relayer(
1983            "test-relayer".to_string(),
1984            patch,
1985            actix_web::web::ThinData(app_state),
1986        )
1987        .await;
1988
1989        assert!(result.is_err());
1990        if let Err(ApiError::BadRequest(msg)) = result {
1991            assert!(msg.contains("Invalid policy") || msg.contains("Invalid update request"));
1992        } else {
1993            panic!("Expected BadRequest error for invalid policy values");
1994        }
1995    }
1996
1997    #[actix_web::test]
1998    async fn test_update_relayer_multiple_fields_at_once() {
1999        let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
2000        relayer.notification_id = Some("old-notification".to_string());
2001        let app_state =
2002            create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
2003
2004        let patch = serde_json::json!({
2005            "name": "Multi-Update Relayer",
2006            "paused": true,
2007            "notification_id": null,
2008            "policies": {
2009                "gas_price_cap": 40000000000u64,
2010                "eip1559_pricing": true
2011            },
2012            "custom_rpc_urls": [
2013                {
2014                    "url": "https://new-rpc.example.com",
2015                    "weight": 90
2016                }
2017            ]
2018        });
2019
2020        let result = update_relayer(
2021            "test-relayer".to_string(),
2022            patch,
2023            actix_web::web::ThinData(app_state),
2024        )
2025        .await;
2026
2027        assert!(result.is_ok());
2028        let response = result.unwrap();
2029        assert_eq!(response.status(), 200);
2030
2031        let body = to_bytes(response.into_body()).await.unwrap();
2032        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
2033
2034        assert!(api_response.success);
2035        let data = api_response.data.unwrap();
2036
2037        // Verify all fields were updated correctly
2038        assert_eq!(data.name, "Multi-Update Relayer");
2039        assert!(data.paused);
2040        assert_eq!(data.notification_id, None);
2041
2042        // Verify policies and RPC URLs were set
2043        assert!(data.policies.is_some());
2044        assert!(data.custom_rpc_urls.is_some());
2045        let rpc_urls = data.custom_rpc_urls.unwrap();
2046        assert_eq!(rpc_urls.len(), 1);
2047        assert_eq!(rpc_urls[0].url, "https://new-rpc.example.com");
2048        assert_eq!(rpc_urls[0].weight, 90);
2049    }
2050
2051    #[actix_web::test]
2052    async fn test_update_relayer_solana_policies() {
2053        use crate::models::{
2054            NetworkType, RelayerNetworkPolicy, RelayerSolanaPolicy, SolanaFeePaymentStrategy,
2055        };
2056
2057        // Create a Solana relayer (not the default EVM one)
2058        let mut solana_relayer = create_mock_relayer("test-solana-relayer".to_string(), false);
2059        solana_relayer.network_type = NetworkType::Solana;
2060        solana_relayer.policies = RelayerNetworkPolicy::Solana(RelayerSolanaPolicy::default());
2061
2062        let app_state =
2063            create_mock_app_state(None, Some(vec![solana_relayer]), None, None, None, None).await;
2064
2065        let patch = serde_json::json!({
2066            "policies": {
2067                "fee_payment_strategy": "user",
2068                "min_balance": 2000000,
2069                "max_signatures": 5,
2070                "max_tx_data_size": 800,
2071                "max_allowed_fee_lamports": 25000,
2072                "fee_margin_percentage": 15.0
2073            }
2074        });
2075
2076        let result = update_relayer(
2077            "test-solana-relayer".to_string(),
2078            patch,
2079            actix_web::web::ThinData(app_state),
2080        )
2081        .await;
2082
2083        assert!(result.is_ok());
2084        let response = result.unwrap();
2085        assert_eq!(response.status(), 200);
2086
2087        let body = to_bytes(response.into_body()).await.unwrap();
2088        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
2089
2090        assert!(api_response.success);
2091        let data = api_response.data.unwrap();
2092
2093        // Verify Solana policies are present and correctly updated
2094        assert!(data.policies.is_some());
2095        let policies = data.policies.unwrap();
2096        if let RelayerNetworkPolicyResponse::Solana(solana_policy) = policies {
2097            assert_eq!(
2098                solana_policy.fee_payment_strategy,
2099                Some(SolanaFeePaymentStrategy::User)
2100            );
2101            assert_eq!(solana_policy.min_balance, 2000000);
2102            assert_eq!(solana_policy.max_signatures, Some(5));
2103            assert_eq!(solana_policy.max_tx_data_size, 800);
2104            assert_eq!(solana_policy.max_allowed_fee_lamports, Some(25000));
2105            assert_eq!(solana_policy.fee_margin_percentage, Some(15.0));
2106        } else {
2107            panic!("Expected Solana policies in response");
2108        }
2109    }
2110
2111    // DELETE RELAYER TESTS
2112
2113    #[actix_web::test]
2114    async fn test_delete_relayer_success() {
2115        let relayer = create_mock_relayer("test-relayer".to_string(), false);
2116        let app_state =
2117            create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
2118
2119        let result = delete_relayer(
2120            "test-relayer".to_string(),
2121            actix_web::web::ThinData(app_state),
2122        )
2123        .await;
2124
2125        assert!(result.is_ok());
2126        let response = result.unwrap();
2127        assert_eq!(response.status(), 200);
2128
2129        let body = to_bytes(response.into_body()).await.unwrap();
2130        let api_response: ApiResponse<String> = serde_json::from_slice(&body).unwrap();
2131
2132        assert!(api_response.success);
2133        let data = api_response.data.unwrap();
2134        assert!(data.contains("Relayer deleted successfully"));
2135    }
2136
2137    #[actix_web::test]
2138    async fn test_delete_relayer_with_transactions() {
2139        let relayer = create_mock_relayer("relayer-with-tx".to_string(), false);
2140        let mut transaction = create_mock_transaction();
2141        transaction.id = "test-tx".to_string();
2142        transaction.relayer_id = "relayer-with-tx".to_string();
2143        let app_state = create_mock_app_state(
2144            None,
2145            Some(vec![relayer]),
2146            None,
2147            None,
2148            None,
2149            Some(vec![transaction]),
2150        )
2151        .await;
2152
2153        let result = delete_relayer(
2154            "relayer-with-tx".to_string(),
2155            actix_web::web::ThinData(app_state),
2156        )
2157        .await;
2158
2159        assert!(result.is_err());
2160        if let Err(ApiError::BadRequest(msg)) = result {
2161            assert!(msg.contains("Cannot delete relayer 'relayer-with-tx'"));
2162            assert!(msg.contains("has 1 transaction(s)"));
2163            assert!(msg.contains("wait for all transactions to complete"));
2164        } else {
2165            panic!("Expected BadRequest error for relayer with transactions");
2166        }
2167    }
2168
2169    #[actix_web::test]
2170    async fn test_delete_relayer_nonexistent() {
2171        let app_state = create_mock_app_state(None, None, None, None, None, None).await;
2172
2173        let result = delete_relayer(
2174            "nonexistent-relayer".to_string(),
2175            actix_web::web::ThinData(app_state),
2176        )
2177        .await;
2178
2179        assert!(result.is_err());
2180        if let Err(ApiError::NotFound(msg)) = result {
2181            assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
2182        } else {
2183            panic!("Expected NotFound error for nonexistent relayer");
2184        }
2185    }
2186
2187    #[actix_web::test]
2188    async fn test_sign_transaction_success() {
2189        let _lock = ENV_MUTEX.lock().await;
2190        setup_test_env();
2191        let network = create_mock_stellar_network();
2192        let signer = create_mock_signer();
2193        let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
2194        relayer.network_type = NetworkType::Stellar;
2195        let app_state = create_mock_app_state(
2196            None,
2197            Some(vec![relayer]),
2198            Some(vec![signer]),
2199            Some(vec![network]),
2200            None,
2201            None,
2202        )
2203        .await;
2204
2205        let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2206            unsigned_xdr: "test-unsigned-xdr".to_string(),
2207        });
2208
2209        let result = sign_transaction(
2210            "test-relayer".to_string(),
2211            request,
2212            actix_web::web::ThinData(app_state),
2213        )
2214        .await;
2215
2216        // The actual signing will fail in the mock environment, but we can test that
2217        // the function is callable with generics and processes the request
2218        assert!(result.is_err());
2219        cleanup_test_env();
2220    }
2221
2222    #[actix_web::test]
2223    async fn test_sign_transaction_relayer_not_found() {
2224        let app_state = create_mock_app_state(None, None, None, None, None, None).await;
2225
2226        let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2227            unsigned_xdr: "test-unsigned-xdr".to_string(),
2228        });
2229
2230        let result = sign_transaction(
2231            "nonexistent-relayer".to_string(),
2232            request,
2233            actix_web::web::ThinData(app_state),
2234        )
2235        .await;
2236
2237        assert!(result.is_err());
2238        if let Err(ApiError::NotFound(msg)) = result {
2239            assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
2240        } else {
2241            panic!("Expected NotFound error for nonexistent relayer");
2242        }
2243    }
2244
2245    #[actix_web::test]
2246    async fn test_sign_transaction_relayer_disabled() {
2247        let mut relayer = create_mock_relayer("disabled-relayer".to_string(), false);
2248        relayer.paused = true;
2249        let app_state =
2250            create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
2251
2252        let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2253            unsigned_xdr: "test-unsigned-xdr".to_string(),
2254        });
2255
2256        let result = sign_transaction(
2257            "disabled-relayer".to_string(),
2258            request,
2259            actix_web::web::ThinData(app_state),
2260        )
2261        .await;
2262
2263        assert!(result.is_err());
2264        if let Err(ApiError::ForbiddenError(msg)) = result {
2265            assert!(msg.contains("Relayer paused"));
2266        } else {
2267            panic!("Expected ForbiddenError for paused relayer");
2268        }
2269    }
2270
2271    #[actix_web::test]
2272    async fn test_sign_transaction_system_disabled() {
2273        let mut relayer = create_mock_relayer("system-disabled-relayer".to_string(), false);
2274        relayer.system_disabled = true;
2275        let app_state =
2276            create_mock_app_state(None, Some(vec![relayer]), None, None, None, None).await;
2277
2278        let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2279            unsigned_xdr: "test-unsigned-xdr".to_string(),
2280        });
2281
2282        let result = sign_transaction(
2283            "system-disabled-relayer".to_string(),
2284            request,
2285            actix_web::web::ThinData(app_state),
2286        )
2287        .await;
2288
2289        assert!(result.is_err());
2290        if let Err(ApiError::ForbiddenError(msg)) = result {
2291            assert!(msg.contains("Relayer disabled"));
2292        } else {
2293            panic!("Expected ForbiddenError for system disabled relayer");
2294        }
2295    }
2296
2297    #[actix_web::test]
2298    async fn test_quote_sponsored_transaction_relayer_not_found() {
2299        let app_state = create_mock_app_state(None, None, None, None, None, None).await;
2300
2301        let request = SponsoredTransactionQuoteRequest::Stellar(
2302            crate::models::StellarFeeEstimateRequestParams {
2303                transaction_xdr: Some("test-xdr".to_string()),
2304                operations: None,
2305                source_account: None,
2306                fee_token: "native".to_string(),
2307            },
2308        );
2309
2310        let result = quote_sponsored_transaction(
2311            "nonexistent-relayer".to_string(),
2312            request,
2313            actix_web::web::ThinData(app_state),
2314        )
2315        .await;
2316
2317        assert!(result.is_err());
2318        if let Err(ApiError::NotFound(msg)) = result {
2319            assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
2320        } else {
2321            panic!("Expected NotFound error for nonexistent relayer");
2322        }
2323    }
2324
2325    #[actix_web::test]
2326    async fn test_quote_sponsored_transaction_relayer_disabled() {
2327        let mut relayer = create_mock_relayer("disabled-relayer".to_string(), false);
2328        relayer.paused = true;
2329        relayer.network_type = NetworkType::Stellar;
2330        relayer.policies = crate::models::RelayerNetworkPolicy::Stellar(RelayerStellarPolicy {
2331            fee_payment_strategy: Some(StellarFeePaymentStrategy::User),
2332            ..Default::default()
2333        });
2334        let network = create_mock_stellar_network();
2335        let signer = create_mock_signer();
2336        let app_state = create_mock_app_state(
2337            None,
2338            Some(vec![relayer]),
2339            Some(vec![signer]),
2340            Some(vec![network]),
2341            None,
2342            None,
2343        )
2344        .await;
2345
2346        let request = SponsoredTransactionQuoteRequest::Stellar(
2347            crate::models::StellarFeeEstimateRequestParams {
2348                transaction_xdr: Some("test-xdr".to_string()),
2349                operations: None,
2350                source_account: None,
2351                fee_token: "native".to_string(),
2352            },
2353        );
2354
2355        let result = quote_sponsored_transaction(
2356            "disabled-relayer".to_string(),
2357            request,
2358            actix_web::web::ThinData(app_state),
2359        )
2360        .await;
2361
2362        assert!(result.is_err());
2363        if let Err(ApiError::ForbiddenError(msg)) = result {
2364            assert!(msg.contains("Relayer paused"));
2365        } else {
2366            panic!("Expected ForbiddenError for paused relayer");
2367        }
2368    }
2369
2370    #[actix_web::test]
2371    async fn test_build_sponsored_transaction_relayer_not_found() {
2372        let app_state = create_mock_app_state(None, None, None, None, None, None).await;
2373
2374        let request = SponsoredTransactionBuildRequest::Stellar(
2375            crate::models::StellarPrepareTransactionRequestParams {
2376                transaction_xdr: Some("test-xdr".to_string()),
2377                operations: None,
2378                source_account: None,
2379                fee_token: "native".to_string(),
2380            },
2381        );
2382
2383        let result = build_sponsored_transaction(
2384            "nonexistent-relayer".to_string(),
2385            request,
2386            actix_web::web::ThinData(app_state),
2387        )
2388        .await;
2389
2390        assert!(result.is_err());
2391        if let Err(ApiError::NotFound(msg)) = result {
2392            assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
2393        } else {
2394            panic!("Expected NotFound error for nonexistent relayer");
2395        }
2396    }
2397
2398    #[actix_web::test]
2399    async fn test_build_sponsored_transaction_relayer_disabled() {
2400        let mut relayer = create_mock_relayer("disabled-relayer".to_string(), false);
2401        relayer.paused = true;
2402        relayer.network_type = NetworkType::Stellar;
2403        relayer.policies = crate::models::RelayerNetworkPolicy::Stellar(RelayerStellarPolicy {
2404            fee_payment_strategy: Some(StellarFeePaymentStrategy::User),
2405            ..Default::default()
2406        });
2407        let network = create_mock_stellar_network();
2408        let signer = create_mock_signer();
2409        let app_state = create_mock_app_state(
2410            None,
2411            Some(vec![relayer]),
2412            Some(vec![signer]),
2413            Some(vec![network]),
2414            None,
2415            None,
2416        )
2417        .await;
2418
2419        let request = SponsoredTransactionBuildRequest::Stellar(
2420            crate::models::StellarPrepareTransactionRequestParams {
2421                transaction_xdr: Some("test-xdr".to_string()),
2422                operations: None,
2423                source_account: None,
2424                fee_token: "native".to_string(),
2425            },
2426        );
2427
2428        let result = build_sponsored_transaction(
2429            "disabled-relayer".to_string(),
2430            request,
2431            actix_web::web::ThinData(app_state),
2432        )
2433        .await;
2434
2435        assert!(result.is_err());
2436        if let Err(ApiError::ForbiddenError(msg)) = result {
2437            assert!(msg.contains("Relayer paused"));
2438        } else {
2439            panic!("Expected ForbiddenError for paused relayer");
2440        }
2441    }
2442}