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, Relayer,
16        RelayerFactory, RelayerFactoryTrait, SignDataRequest, SignDataResponse,
17        SignTransactionRequest, SignTypedDataRequest, Transaction,
18    },
19    jobs::JobProducerTrait,
20    models::{
21        convert_to_internal_rpc_request, deserialize_policy_for_network_type, ApiError,
22        ApiResponse, CreateRelayerRequest, DefaultAppState, NetworkRepoModel,
23        NetworkTransactionRequest, NetworkType, NotificationRepoModel, PaginationMeta,
24        PaginationQuery, Relayer as RelayerDomainModel, RelayerRepoModel, RelayerRepoUpdater,
25        RelayerResponse, Signer as SignerDomainModel, SignerRepoModel, ThinDataAppState,
26        TransactionRepoModel, TransactionResponse, TransactionStatus, UpdateRelayerRequestRaw,
27    },
28    repositories::{
29        NetworkRepository, PluginRepositoryTrait, RelayerRepository, Repository,
30        TransactionCounterTrait, TransactionRepository,
31    },
32    services::{Signer, SignerFactory},
33};
34use actix_web::{web, HttpResponse};
35use eyre::Result;
36
37/// Lists all relayers with pagination support.
38///
39/// # Arguments
40///
41/// * `query` - The pagination query parameters.
42/// * `state` - The application state containing the relayer repository.
43///
44/// # Returns
45///
46/// A paginated list of relayers.
47pub async fn list_relayers<J, RR, TR, NR, NFR, SR, TCR, PR>(
48    query: PaginationQuery,
49    state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
50) -> Result<HttpResponse, ApiError>
51where
52    J: JobProducerTrait + Send + Sync + 'static,
53    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
54    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
55    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
56    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
57    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
58    TCR: TransactionCounterTrait + Send + Sync + 'static,
59    PR: PluginRepositoryTrait + Send + Sync + 'static,
60{
61    let relayers = state.relayer_repository.list_paginated(query).await?;
62
63    let mapped_relayers: Vec<RelayerResponse> =
64        relayers.items.into_iter().map(|r| r.into()).collect();
65
66    Ok(HttpResponse::Ok().json(ApiResponse::paginated(
67        mapped_relayers,
68        PaginationMeta {
69            total_items: relayers.total,
70            current_page: relayers.page,
71            per_page: relayers.per_page,
72        },
73    )))
74}
75
76/// Retrieves details of a specific relayer by its ID.
77///
78/// # Arguments
79///
80/// * `relayer_id` - The ID of the relayer to retrieve.
81/// * `state` - The application state containing the relayer repository.
82///
83/// # Returns
84///
85/// The details of the specified relayer.
86pub async fn get_relayer<J, RR, TR, NR, NFR, SR, TCR, PR>(
87    relayer_id: String,
88    state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
89) -> Result<HttpResponse, ApiError>
90where
91    J: JobProducerTrait + Send + Sync + 'static,
92    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
93    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
94    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
95    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
96    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
97    TCR: TransactionCounterTrait + Send + Sync + 'static,
98    PR: PluginRepositoryTrait + Send + Sync + 'static,
99{
100    let relayer = get_relayer_by_id(relayer_id, &state).await?;
101
102    let relayer_response: RelayerResponse = relayer.into();
103
104    Ok(HttpResponse::Ok().json(ApiResponse::success(relayer_response)))
105}
106
107/// Creates a new relayer.
108///
109/// # Arguments
110///
111/// * `request` - The relayer creation request.
112/// * `state` - The application state containing the relayer repository.
113///
114/// # Returns
115///
116/// The created relayer or an error if creation fails.
117///
118/// # Validation
119///
120/// This endpoint performs comprehensive dependency validation before creating the relayer:
121/// - **Signer Validation**: Ensures the specified signer exists in the system
122/// - **Signer Uniqueness**: Validates that the signer is not already in use by another relayer on the same network
123/// - **Notification Validation**: If a notification ID is provided, validates it exists
124/// - **Network Validation**: Confirms the specified network exists for the given network type
125///
126/// All validations must pass before the relayer is created, ensuring referential integrity and security constraints.
127pub async fn create_relayer<J, RR, TR, NR, NFR, SR, TCR, PR>(
128    request: CreateRelayerRequest,
129    state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
130) -> Result<HttpResponse, ApiError>
131where
132    J: JobProducerTrait + Send + Sync + 'static,
133    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
134    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
135    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
136    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
137    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
138    TCR: TransactionCounterTrait + Send + Sync + 'static,
139    PR: PluginRepositoryTrait + Send + Sync + 'static,
140{
141    // Convert request to domain relayer (validates automatically)
142    let relayer = RelayerDomainModel::try_from(request)?;
143
144    // Check if signer exists
145    let signer_model = state
146        .signer_repository
147        .get_by_id(relayer.signer_id.clone())
148        .await?;
149
150    // Check if network exists for the given network type
151    let network = state
152        .network_repository
153        .get_by_name(relayer.network_type, &relayer.network)
154        .await?;
155
156    if network.is_none() {
157        return Err(ApiError::BadRequest(format!(
158            "Network '{}' not found for network type '{}'. Please ensure the network configuration exists.",
159            relayer.network,
160            relayer.network_type
161        )));
162    }
163
164    // Check if signer is already in use by another relayer on the same network
165    let relayers = state
166        .relayer_repository
167        .list_by_signer_id(&relayer.signer_id)
168        .await?;
169    if let Some(existing_relayer) = relayers.iter().find(|r| r.network == relayer.network) {
170        return Err(ApiError::BadRequest(format!(
171            "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.",
172            relayer.signer_id, existing_relayer.id, relayer.network
173        )));
174    }
175
176    // Check if notification exists (if provided)
177    if let Some(notification_id) = &relayer.notification_id {
178        let _notification = state
179            .notification_repository
180            .get_by_id(notification_id.clone())
181            .await?;
182    }
183
184    // Convert domain model to repository model
185    let mut relayer_model = RelayerRepoModel::from(relayer);
186
187    // get address from signer and set it to relayer model
188    let signer_service = SignerFactory::create_signer(
189        &relayer_model.network_type,
190        &SignerDomainModel::from(signer_model.clone()),
191    )
192    .await
193    .map_err(|e| ApiError::InternalError(e.to_string()))?;
194    let address = signer_service
195        .address()
196        .await
197        .map_err(|e| ApiError::InternalError(e.to_string()))?;
198    relayer_model.address = address.to_string();
199
200    let created_relayer = state.relayer_repository.create(relayer_model).await?;
201
202    let relayer =
203        RelayerFactory::create_relayer(created_relayer.clone(), signer_model, &state).await?;
204
205    relayer.initialize_relayer().await?;
206
207    let response = RelayerResponse::from(created_relayer);
208    Ok(HttpResponse::Created().json(ApiResponse::success(response)))
209}
210
211/// Updates a relayer's information.
212///
213/// # Arguments
214///
215/// * `relayer_id` - The ID of the relayer to update.
216/// * `update_req` - The update request containing new relayer data.
217/// * `state` - The application state containing the relayer repository.
218///
219/// # Returns
220///
221/// The updated relayer information.
222pub async fn update_relayer<J, RR, TR, NR, NFR, SR, TCR, PR>(
223    relayer_id: String,
224    patch: serde_json::Value,
225    state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
226) -> Result<HttpResponse, ApiError>
227where
228    J: JobProducerTrait + Send + Sync + 'static,
229    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
230    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
231    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
232    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
233    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
234    TCR: TransactionCounterTrait + Send + Sync + 'static,
235    PR: PluginRepositoryTrait + Send + Sync + 'static,
236{
237    let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
238
239    // convert patch to UpdateRelayerRequest to validate
240    let update_request: UpdateRelayerRequestRaw = serde_json::from_value(patch.clone())
241        .map_err(|e| ApiError::BadRequest(format!("Invalid update request: {}", e)))?;
242
243    if let Some(policies) = update_request.policies {
244        deserialize_policy_for_network_type(&policies, relayer.network_type)
245            .map_err(|e| ApiError::BadRequest(format!("Invalid policy: {}", e)))?;
246    }
247
248    if relayer.system_disabled {
249        return Err(ApiError::BadRequest("Relayer is disabled".to_string()));
250    }
251
252    // Check if notification exists (if setting one) by extracting from JSON patch
253    if let Some(notification_id) = update_request.notification_id {
254        state
255            .notification_repository
256            .get_by_id(notification_id.to_string())
257            .await?;
258    }
259
260    // Apply JSON merge patch directly to domain object
261    let updated_domain = RelayerDomainModel::from(relayer.clone())
262        .apply_json_patch(&patch)
263        .map_err(ApiError::from)?;
264
265    // Use existing RelayerRepoUpdater to preserve runtime fields
266    let updated_repo_model =
267        RelayerRepoUpdater::from_existing(relayer).apply_domain_update(updated_domain);
268
269    let saved_relayer = state
270        .relayer_repository
271        .update(relayer_id.clone(), updated_repo_model)
272        .await?;
273
274    let relayer_response: RelayerResponse = saved_relayer.into();
275    Ok(HttpResponse::Ok().json(ApiResponse::success(relayer_response)))
276}
277
278/// Deletes a relayer by ID.
279///
280/// # Arguments
281///
282/// * `relayer_id` - The ID of the relayer to delete.
283/// * `state` - The application state containing the relayer repository.
284///
285/// # Returns
286///
287/// A success response or an error if deletion fails.
288///
289/// # Security
290///
291/// This endpoint ensures that relayers cannot be deleted if they have any pending
292/// or active transactions. This prevents data loss and maintains system integrity.
293pub async fn delete_relayer<J, RR, TR, NR, NFR, SR, TCR, PR>(
294    relayer_id: String,
295    state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
296) -> Result<HttpResponse, ApiError>
297where
298    J: JobProducerTrait + Send + Sync + 'static,
299    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
300    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
301    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
302    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
303    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
304    TCR: TransactionCounterTrait + Send + Sync + 'static,
305    PR: PluginRepositoryTrait + Send + Sync + 'static,
306{
307    // Check if the relayer exists
308    let _relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
309
310    // Check if the relayer has any transactions (pending or otherwise)
311    let transactions = state
312        .transaction_repository
313        .find_by_status(
314            &relayer_id,
315            &[
316                TransactionStatus::Pending,
317                TransactionStatus::Sent,
318                TransactionStatus::Submitted,
319            ],
320        )
321        .await?;
322
323    if !transactions.is_empty() {
324        return Err(ApiError::BadRequest(format!(
325            "Cannot delete relayer '{}' because it has {} transaction(s). Please wait for all transactions to complete or cancel them before deleting the relayer.",
326            relayer_id,
327            transactions.len()
328        )));
329    }
330
331    // Safe to delete - no transactions associated with this relayer
332    state.relayer_repository.delete_by_id(relayer_id).await?;
333
334    Ok(HttpResponse::Ok().json(ApiResponse::success("Relayer deleted successfully")))
335}
336
337/// Retrieves the status of a specific relayer.
338///
339/// # Arguments
340///
341/// * `relayer_id` - The ID of the relayer to check status for.
342/// * `state` - The application state containing the relayer repository.
343///
344/// # Returns
345///
346/// The status of the specified relayer.
347pub async fn get_relayer_status<J, RR, TR, NR, NFR, SR, TCR, PR>(
348    relayer_id: String,
349    state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
350) -> Result<HttpResponse, ApiError>
351where
352    J: JobProducerTrait + Send + Sync + 'static,
353    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
354    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
355    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
356    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
357    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
358    TCR: TransactionCounterTrait + Send + Sync + 'static,
359    PR: PluginRepositoryTrait + Send + Sync + 'static,
360{
361    let relayer = get_network_relayer(relayer_id, &state).await?;
362
363    let status = relayer.get_status().await?;
364
365    Ok(HttpResponse::Ok().json(ApiResponse::success(status)))
366}
367
368/// Retrieves the balance of a specific relayer.
369///
370/// # Arguments
371///
372/// * `relayer_id` - The ID of the relayer to check balance for.
373/// * `state` - The application state containing the relayer repository.
374///
375/// # Returns
376///
377/// The balance of the specified relayer.
378pub async fn get_relayer_balance<J, RR, TR, NR, NFR, SR, TCR, PR>(
379    relayer_id: String,
380    state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
381) -> Result<HttpResponse, ApiError>
382where
383    J: JobProducerTrait + Send + Sync + 'static,
384    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
385    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
386    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
387    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
388    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
389    TCR: TransactionCounterTrait + Send + Sync + 'static,
390    PR: PluginRepositoryTrait + Send + Sync + 'static,
391{
392    let relayer = get_network_relayer(relayer_id, &state).await?;
393
394    let result = relayer.get_balance().await?;
395
396    Ok(HttpResponse::Ok().json(ApiResponse::success(result)))
397}
398
399/// Sends a transaction through a specified relayer.
400///
401/// # Arguments
402///
403/// * `relayer_id` - The ID of the relayer to send the transaction through.
404/// * `request` - The transaction request data.
405/// * `state` - The application state containing the relayer repository.
406///
407/// # Returns
408///
409/// The response of the transaction processing.
410pub async fn send_transaction(
411    relayer_id: String,
412    request: serde_json::Value,
413    state: web::ThinData<DefaultAppState>,
414) -> Result<HttpResponse, ApiError> {
415    let relayer_repo_model = get_relayer_by_id(relayer_id, &state).await?;
416    relayer_repo_model.validate_active_state()?;
417
418    let relayer = get_network_relayer(relayer_repo_model.id.clone(), &state).await?;
419
420    let tx_request: NetworkTransactionRequest =
421        NetworkTransactionRequest::from_json(&relayer_repo_model.network_type, request.clone())?;
422
423    tx_request.validate(&relayer_repo_model)?;
424
425    let transaction = relayer.process_transaction_request(tx_request).await?;
426
427    let transaction_response: TransactionResponse = transaction.into();
428
429    Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
430}
431
432/// Retrieves a transaction by its ID for a specific relayer.
433///
434/// # Arguments
435///
436/// * `relayer_id` - The ID of the relayer.
437/// * `transaction_id` - The ID of the transaction to retrieve.
438/// * `state` - The application state containing the transaction repository.
439///
440/// # Returns
441///
442/// The details of the specified transaction.
443pub async fn get_transaction_by_id<J, RR, TR, NR, NFR, SR, TCR, PR>(
444    relayer_id: String,
445    transaction_id: String,
446    state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
447) -> Result<HttpResponse, ApiError>
448where
449    J: JobProducerTrait + Send + Sync + 'static,
450    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
451    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
452    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
453    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
454    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
455    TCR: TransactionCounterTrait + Send + Sync + 'static,
456    PR: PluginRepositoryTrait + Send + Sync + 'static,
457{
458    if relayer_id.is_empty() || transaction_id.is_empty() {
459        return Ok(HttpResponse::Ok().json(ApiResponse::<()>::error(
460            "Invalid relayer or transaction ID".to_string(),
461        )));
462    }
463    // validation purpose only, checks if relayer exists
464    get_relayer_by_id(relayer_id, &state).await?;
465
466    let transaction = get_tx_by_id(transaction_id, &state).await?;
467
468    let transaction_response: TransactionResponse = transaction.into();
469
470    Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
471}
472
473/// Retrieves a transaction by its nonce for a specific relayer.
474///
475/// # Arguments
476///
477/// * `relayer_id` - The ID of the relayer.
478/// * `nonce` - The nonce of the transaction to retrieve.
479/// * `state` - The application state containing the transaction repository.
480///
481/// # Returns
482///
483/// The details of the specified transaction.
484pub async fn get_transaction_by_nonce<J, RR, TR, NR, NFR, SR, TCR, PR>(
485    relayer_id: String,
486    nonce: u64,
487    state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
488) -> Result<HttpResponse, ApiError>
489where
490    J: JobProducerTrait + Send + Sync + 'static,
491    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
492    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
493    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
494    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
495    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
496    TCR: TransactionCounterTrait + Send + Sync + 'static,
497    PR: PluginRepositoryTrait + Send + Sync + 'static,
498{
499    let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
500
501    // get by nonce is only supported for EVM network
502    if relayer.network_type != NetworkType::Evm {
503        return Err(ApiError::NotSupported(
504            "Nonce lookup only supported for EVM networks".into(),
505        ));
506    }
507
508    let transaction = state
509        .transaction_repository
510        .find_by_nonce(&relayer_id, nonce)
511        .await?
512        .ok_or_else(|| ApiError::NotFound(format!("Transaction with nonce {} not found", nonce)))?;
513
514    let transaction_response: TransactionResponse = transaction.into();
515
516    Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
517}
518
519/// Lists all transactions for a specific relayer with pagination support.
520///
521/// # Arguments
522///
523/// * `relayer_id` - The ID of the relayer.
524/// * `query` - The pagination query parameters.
525/// * `state` - The application state containing the transaction repository.
526///
527/// # Returns
528///
529/// A paginated list of transactions
530pub async fn list_transactions<J, RR, TR, NR, NFR, SR, TCR, PR>(
531    relayer_id: String,
532    query: PaginationQuery,
533    state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
534) -> Result<HttpResponse, ApiError>
535where
536    J: JobProducerTrait + Send + Sync + 'static,
537    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
538    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
539    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
540    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
541    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
542    TCR: TransactionCounterTrait + Send + Sync + 'static,
543    PR: PluginRepositoryTrait + Send + Sync + 'static,
544{
545    get_relayer_by_id(relayer_id.clone(), &state).await?;
546
547    let transactions = state
548        .transaction_repository
549        .find_by_relayer_id(&relayer_id, query)
550        .await?;
551
552    let transaction_response_list: Vec<TransactionResponse> =
553        transactions.items.into_iter().map(|t| t.into()).collect();
554
555    Ok(HttpResponse::Ok().json(ApiResponse::paginated(
556        transaction_response_list,
557        PaginationMeta {
558            total_items: transactions.total,
559            current_page: transactions.page,
560            per_page: transactions.per_page,
561        },
562    )))
563}
564
565/// Deletes all pending transactions for a specific relayer.
566///
567/// # Arguments
568///
569/// * `relayer_id` - The ID of the relayer.
570/// * `state` - The application state containing the relayer repository.
571///
572/// # Returns
573///
574/// A success response with details about cancelled and failed transactions.
575pub async fn delete_pending_transactions<J, RR, TR, NR, NFR, SR, TCR, PR>(
576    relayer_id: String,
577    state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
578) -> Result<HttpResponse, ApiError>
579where
580    J: JobProducerTrait + Send + Sync + 'static,
581    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
582    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
583    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
584    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
585    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
586    TCR: TransactionCounterTrait + Send + Sync + 'static,
587    PR: PluginRepositoryTrait + Send + Sync + 'static,
588{
589    let relayer = get_relayer_by_id(relayer_id, &state).await?;
590    relayer.validate_active_state()?;
591    let network_relayer = get_network_relayer_by_model(relayer.clone(), &state).await?;
592
593    let result = network_relayer.delete_pending_transactions().await?;
594
595    Ok(HttpResponse::Ok().json(ApiResponse::success(result)))
596}
597
598/// Cancels a specific transaction for a relayer.
599///
600/// # Arguments
601///
602/// * `relayer_id` - The ID of the relayer.
603/// * `transaction_id` - The ID of the transaction to cancel.
604/// * `state` - The application state containing the transaction repository.
605///
606/// # Returns
607///
608/// The details of the canceled transaction.
609pub async fn cancel_transaction(
610    relayer_id: String,
611    transaction_id: String,
612    state: web::ThinData<DefaultAppState>,
613) -> Result<HttpResponse, ApiError> {
614    let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
615    relayer.validate_active_state()?;
616
617    let relayer_transaction = get_relayer_transaction_by_model(relayer.clone(), &state).await?;
618
619    let transaction_to_cancel = get_tx_by_id(transaction_id, &state).await?;
620
621    let canceled_transaction = relayer_transaction
622        .cancel_transaction(transaction_to_cancel)
623        .await?;
624
625    let transaction_response: TransactionResponse = canceled_transaction.into();
626
627    Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
628}
629
630/// Replaces a specific transaction for a relayer.
631///
632/// # Arguments
633///
634/// * `relayer_id` - The ID of the relayer.
635/// * `transaction_id` - The ID of the transaction to replace.
636/// * `request` - The new transaction request data.
637/// * `state` - The application state containing the transaction repository.
638///
639/// # Returns
640///
641/// The details of the replaced transaction.
642pub async fn replace_transaction(
643    relayer_id: String,
644    transaction_id: String,
645    request: serde_json::Value,
646    state: web::ThinData<DefaultAppState>,
647) -> Result<HttpResponse, ApiError> {
648    let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
649    relayer.validate_active_state()?;
650
651    let new_tx_request: NetworkTransactionRequest =
652        NetworkTransactionRequest::from_json(&relayer.network_type, request.clone())?;
653    new_tx_request.validate(&relayer)?;
654
655    let transaction_to_replace = state
656        .transaction_repository
657        .get_by_id(transaction_id)
658        .await?;
659
660    let relayer_transaction = get_relayer_transaction_by_model(relayer.clone(), &state).await?;
661    let replaced_transaction = relayer_transaction
662        .replace_transaction(transaction_to_replace, new_tx_request)
663        .await?;
664
665    let transaction_response: TransactionResponse = replaced_transaction.into();
666
667    Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
668}
669
670/// Signs data using a specific relayer.
671///
672/// # Arguments
673///
674/// * `relayer_id` - The ID of the relayer.
675/// * `request` - The sign data request.
676/// * `state` - The application state containing the relayer repository.
677///
678/// # Returns
679///
680/// The signed data response.
681pub async fn sign_data<J, RR, TR, NR, NFR, SR, TCR, PR>(
682    relayer_id: String,
683    request: SignDataRequest,
684    state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
685) -> Result<HttpResponse, ApiError>
686where
687    J: JobProducerTrait + Send + Sync + 'static,
688    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
689    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
690    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
691    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
692    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
693    TCR: TransactionCounterTrait + Send + Sync + 'static,
694    PR: PluginRepositoryTrait + Send + Sync + 'static,
695{
696    let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
697    relayer.validate_active_state()?;
698    let network_relayer = get_network_relayer_by_model(relayer, &state).await?;
699
700    let result = network_relayer.sign_data(request).await?;
701
702    if let SignDataResponse::Evm(sign) = result {
703        Ok(HttpResponse::Ok().json(ApiResponse::success(sign)))
704    } else {
705        Err(ApiError::NotSupported("Sign data not supported".into()))
706    }
707}
708
709/// Signs typed data using a specific relayer.
710///
711/// # Arguments
712///
713/// * `relayer_id` - The ID of the relayer.
714/// * `request` - The sign typed data request.
715/// * `state` - The application state containing the relayer repository.
716///
717/// # Returns
718///
719/// The signed typed data response.
720pub async fn sign_typed_data<J, RR, TR, NR, NFR, SR, TCR, PR>(
721    relayer_id: String,
722    request: SignTypedDataRequest,
723    state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
724) -> Result<HttpResponse, ApiError>
725where
726    J: JobProducerTrait + Send + Sync + 'static,
727    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
728    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
729    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
730    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
731    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
732    TCR: TransactionCounterTrait + Send + Sync + 'static,
733    PR: PluginRepositoryTrait + Send + Sync + 'static,
734{
735    let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
736    relayer.validate_active_state()?;
737    let network_relayer = get_network_relayer_by_model(relayer, &state).await?;
738
739    let result = network_relayer.sign_typed_data(request).await?;
740
741    Ok(HttpResponse::Ok().json(ApiResponse::success(result)))
742}
743
744/// Performs a JSON-RPC call through a specific relayer.
745///
746/// # Arguments
747///
748/// * `relayer_id` - The ID of the relayer.
749/// * `request` - The raw JSON-RPC request value.
750/// * `state` - The application state containing the relayer repository.
751///
752/// # Returns
753///
754/// The result of the JSON-RPC call.
755pub async fn relayer_rpc<J, RR, TR, NR, NFR, SR, TCR, PR>(
756    relayer_id: String,
757    request: serde_json::Value,
758    state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
759) -> Result<HttpResponse, ApiError>
760where
761    J: JobProducerTrait + Send + Sync + 'static,
762    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
763    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
764    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
765    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
766    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
767    TCR: TransactionCounterTrait + Send + Sync + 'static,
768    PR: PluginRepositoryTrait + Send + Sync + 'static,
769{
770    let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
771    relayer.validate_active_state()?;
772    let network_relayer = get_network_relayer_by_model(relayer.clone(), &state).await?;
773
774    let internal_request = convert_to_internal_rpc_request(request, &relayer.network_type)?;
775    let result = network_relayer.rpc(internal_request).await?;
776
777    Ok(HttpResponse::Ok().json(result))
778}
779
780/// Signs a transaction using a specific relayer
781///
782/// # Arguments
783///
784/// * `relayer_id` - The ID of the relayer.
785/// * `request` - The sign transaction request containing unsigned XDR.
786/// * `state` - The application state containing the relayer repository.
787///
788/// # Returns
789///
790/// The signed transaction response.
791pub async fn sign_transaction<J, RR, TR, NR, NFR, SR, TCR, PR>(
792    relayer_id: String,
793    request: SignTransactionRequest,
794    state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
795) -> Result<HttpResponse, ApiError>
796where
797    J: JobProducerTrait + Send + Sync + 'static,
798    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
799    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
800    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
801    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
802    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
803    TCR: TransactionCounterTrait + Send + Sync + 'static,
804    PR: PluginRepositoryTrait + Send + Sync + 'static,
805{
806    let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
807    relayer.validate_active_state()?;
808
809    // Get the network relayer and use its sign_transaction method
810    let network_relayer = get_network_relayer_by_model(relayer, &state).await?;
811    let result = network_relayer.sign_transaction(&request).await?;
812
813    Ok(HttpResponse::Ok().json(ApiResponse::success(result)))
814}
815
816#[cfg(test)]
817mod tests {
818    use super::*;
819    use crate::{
820        domain::SignTransactionRequestStellar,
821        models::{
822            ApiResponse, CreateRelayerPolicyRequest, CreateRelayerRequest, RelayerEvmPolicy,
823            RelayerNetworkPolicyResponse, RelayerNetworkType, RelayerResponse, RelayerSolanaPolicy,
824            RelayerStellarPolicy, SolanaFeePaymentStrategy,
825        },
826        utils::mocks::mockutils::{
827            create_mock_app_state, create_mock_network, create_mock_notification,
828            create_mock_relayer, create_mock_signer, create_mock_transaction,
829        },
830    };
831    use actix_web::body::to_bytes;
832    use lazy_static::lazy_static;
833    use std::env;
834    use tokio::sync::Mutex;
835
836    lazy_static! {
837        static ref ENV_MUTEX: Mutex<()> = Mutex::new(());
838    }
839
840    fn setup_test_env() {
841        env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D"); // noboost nosemgrep
842        env::set_var("REDIS_URL", "redis://localhost:6379");
843    }
844
845    fn cleanup_test_env() {
846        env::remove_var("API_KEY");
847        env::remove_var("REDIS_URL");
848    }
849
850    /// Helper function to create a test relayer create request
851    fn create_test_relayer_create_request(
852        id: Option<String>,
853        name: &str,
854        network: &str,
855        signer_id: &str,
856        notification_id: Option<String>,
857    ) -> CreateRelayerRequest {
858        CreateRelayerRequest {
859            id,
860            name: name.to_string(),
861            network: network.to_string(),
862            network_type: RelayerNetworkType::Evm,
863            paused: false,
864            policies: None,
865            signer_id: signer_id.to_string(),
866            notification_id,
867            custom_rpc_urls: None,
868        }
869    }
870
871    /// Helper function to create a mock Solana network
872    fn create_mock_solana_network() -> crate::models::NetworkRepoModel {
873        use crate::config::{NetworkConfigCommon, SolanaNetworkConfig};
874        use crate::models::{NetworkConfigData, NetworkRepoModel, NetworkType};
875
876        NetworkRepoModel {
877            id: "test".to_string(),
878            name: "test".to_string(),
879            network_type: NetworkType::Solana,
880            config: NetworkConfigData::Solana(SolanaNetworkConfig {
881                common: NetworkConfigCommon {
882                    network: "test".to_string(),
883                    from: None,
884                    rpc_urls: Some(vec!["http://localhost:8899".to_string()]),
885                    explorer_urls: None,
886                    average_blocktime_ms: Some(400),
887                    is_testnet: Some(true),
888                    tags: None,
889                },
890            }),
891        }
892    }
893
894    /// Helper function to create a mock Stellar network
895    fn create_mock_stellar_network() -> crate::models::NetworkRepoModel {
896        use crate::config::{NetworkConfigCommon, StellarNetworkConfig};
897        use crate::models::{NetworkConfigData, NetworkRepoModel, NetworkType};
898
899        NetworkRepoModel {
900            id: "test".to_string(),
901            name: "test".to_string(),
902            network_type: NetworkType::Stellar,
903            config: NetworkConfigData::Stellar(StellarNetworkConfig {
904                common: NetworkConfigCommon {
905                    network: "test".to_string(),
906                    from: None,
907                    rpc_urls: Some(vec!["https://horizon-testnet.stellar.org".to_string()]),
908                    explorer_urls: None,
909                    average_blocktime_ms: Some(5000),
910                    is_testnet: Some(true),
911                    tags: None,
912                },
913                passphrase: Some("Test Network ; September 2015".to_string()),
914            }),
915        }
916    }
917
918    // CREATE RELAYER TESTS
919
920    #[actix_web::test]
921    async fn test_create_relayer_success() {
922        let _lock = ENV_MUTEX.lock().await;
923        setup_test_env();
924        let network = create_mock_network();
925        let signer = create_mock_signer();
926        let app_state =
927            create_mock_app_state(None, Some(vec![signer]), Some(vec![network]), None, None).await;
928
929        let request = create_test_relayer_create_request(
930            Some("test-relayer".to_string()),
931            "Test Relayer",
932            "test", // Using "test" to match the mock network name
933            "test", // Using "test" to match the mock signer id
934            None,
935        );
936
937        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
938
939        assert!(result.is_ok());
940        let response = result.unwrap();
941        assert_eq!(response.status(), 201);
942
943        let body = to_bytes(response.into_body()).await.unwrap();
944        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
945
946        assert!(api_response.success);
947        let data = api_response.data.unwrap();
948        assert_eq!(data.id, "test-relayer");
949        assert_eq!(data.name, "Test Relayer"); // This one keeps custom name from the request
950        assert_eq!(data.network, "test");
951        cleanup_test_env();
952    }
953
954    #[actix_web::test]
955    async fn test_create_relayer_with_evm_policies() {
956        let _lock = ENV_MUTEX.lock().await;
957        setup_test_env();
958        let network = create_mock_network();
959        let signer = create_mock_signer();
960        let app_state =
961            create_mock_app_state(None, Some(vec![signer]), Some(vec![network]), None, None).await;
962
963        let mut request = create_test_relayer_create_request(
964            Some("test-relayer-policies".to_string()),
965            "Test Relayer with Policies",
966            "test", // Using "test" to match the mock network name
967            "test", // Using "test" to match the mock signer id
968            None,
969        );
970
971        // Add EVM policies
972        request.policies = Some(CreateRelayerPolicyRequest::Evm(RelayerEvmPolicy {
973            gas_price_cap: Some(50000000000),
974            min_balance: Some(1000000000000000000),
975            eip1559_pricing: Some(true),
976            private_transactions: Some(false),
977            gas_limit_estimation: Some(true),
978            whitelist_receivers: Some(vec![
979                "0x1234567890123456789012345678901234567890".to_string()
980            ]),
981        }));
982
983        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
984
985        assert!(result.is_ok());
986        let response = result.unwrap();
987        assert_eq!(response.status(), 201);
988
989        let body = to_bytes(response.into_body()).await.unwrap();
990        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
991
992        assert!(api_response.success);
993        let data = api_response.data.unwrap();
994        assert_eq!(data.id, "test-relayer-policies");
995        assert_eq!(data.name, "Test Relayer with Policies");
996        assert_eq!(data.network, "test");
997
998        // Verify policies are present in response
999        assert!(data.policies.is_some());
1000        cleanup_test_env();
1001    }
1002
1003    #[actix_web::test]
1004    async fn test_create_relayer_with_partial_evm_policies() {
1005        let _lock = ENV_MUTEX.lock().await;
1006        setup_test_env();
1007        let network = create_mock_network();
1008        let signer = create_mock_signer();
1009        let app_state =
1010            create_mock_app_state(None, Some(vec![signer]), Some(vec![network]), None, None).await;
1011
1012        let mut request = create_test_relayer_create_request(
1013            Some("test-relayer-partial".to_string()),
1014            "Test Relayer with Partial Policies",
1015            "test",
1016            "test",
1017            None,
1018        );
1019
1020        // Add partial EVM policies
1021        request.policies = Some(CreateRelayerPolicyRequest::Evm(RelayerEvmPolicy {
1022            gas_price_cap: Some(30000000000),
1023            eip1559_pricing: Some(false),
1024            min_balance: None,
1025            private_transactions: None,
1026            gas_limit_estimation: None,
1027            whitelist_receivers: None,
1028        }));
1029
1030        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1031
1032        assert!(result.is_ok());
1033        let response = result.unwrap();
1034        assert_eq!(response.status(), 201);
1035
1036        let body = to_bytes(response.into_body()).await.unwrap();
1037        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1038
1039        assert!(api_response.success);
1040        let data = api_response.data.unwrap();
1041        assert_eq!(data.id, "test-relayer-partial");
1042
1043        // Verify partial policies are present in response
1044        assert!(data.policies.is_some());
1045        cleanup_test_env();
1046    }
1047
1048    #[actix_web::test]
1049    async fn test_create_relayer_with_solana_policies() {
1050        let _lock = ENV_MUTEX.lock().await;
1051        setup_test_env();
1052        let network = create_mock_solana_network();
1053        let signer = create_mock_signer();
1054        let app_state =
1055            create_mock_app_state(None, Some(vec![signer]), Some(vec![network]), None, None).await;
1056
1057        let mut request = create_test_relayer_create_request(
1058            Some("test-solana-relayer".to_string()),
1059            "Test Solana Relayer",
1060            "test",
1061            "test",
1062            None,
1063        );
1064
1065        // Change network type to Solana and add Solana policies
1066        request.network_type = RelayerNetworkType::Solana;
1067        request.policies = Some(CreateRelayerPolicyRequest::Solana(RelayerSolanaPolicy {
1068            fee_payment_strategy: Some(SolanaFeePaymentStrategy::Relayer),
1069            min_balance: Some(5000000),
1070            max_signatures: Some(10),
1071            max_tx_data_size: Some(1232),
1072            max_allowed_fee_lamports: Some(50000),
1073            allowed_programs: None, // Simplified to avoid validation issues
1074            allowed_tokens: None,
1075            fee_margin_percentage: Some(10.0),
1076            allowed_accounts: None,
1077            disallowed_accounts: None,
1078            swap_config: None,
1079        }));
1080
1081        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1082
1083        assert!(result.is_ok());
1084        let response = result.unwrap();
1085        assert_eq!(response.status(), 201);
1086
1087        let body = to_bytes(response.into_body()).await.unwrap();
1088        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1089
1090        assert!(api_response.success);
1091        let data = api_response.data.unwrap();
1092        assert_eq!(data.id, "test-solana-relayer");
1093        assert_eq!(data.network_type, RelayerNetworkType::Solana);
1094        assert_eq!(data.name, "Test Solana Relayer");
1095
1096        // Verify Solana policies are present in response
1097        assert!(data.policies.is_some());
1098        // verify policies are correct
1099        let policies = data.policies.unwrap();
1100        if let RelayerNetworkPolicyResponse::Solana(solana_policy) = policies {
1101            assert_eq!(
1102                solana_policy.fee_payment_strategy,
1103                Some(SolanaFeePaymentStrategy::Relayer)
1104            );
1105            assert_eq!(solana_policy.min_balance, 5000000);
1106            assert_eq!(solana_policy.max_signatures, Some(10));
1107            assert_eq!(solana_policy.max_tx_data_size, 1232);
1108            assert_eq!(solana_policy.max_allowed_fee_lamports, Some(50000));
1109        } else {
1110            panic!("Expected Solana policies");
1111        }
1112        cleanup_test_env();
1113    }
1114
1115    #[actix_web::test]
1116    async fn test_create_relayer_with_stellar_policies() {
1117        let _lock = ENV_MUTEX.lock().await;
1118        setup_test_env();
1119        let network = create_mock_stellar_network();
1120        let signer = create_mock_signer();
1121        let app_state =
1122            create_mock_app_state(None, Some(vec![signer]), Some(vec![network]), None, None).await;
1123
1124        let mut request = create_test_relayer_create_request(
1125            Some("test-stellar-relayer".to_string()),
1126            "Test Stellar Relayer",
1127            "test",
1128            "test",
1129            None,
1130        );
1131
1132        // Change network type to Stellar and add Stellar policies
1133        request.network_type = RelayerNetworkType::Stellar;
1134        request.policies = Some(CreateRelayerPolicyRequest::Stellar(RelayerStellarPolicy {
1135            min_balance: Some(10000000),
1136            max_fee: Some(100),
1137            timeout_seconds: Some(30),
1138        }));
1139
1140        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1141
1142        assert!(result.is_ok());
1143        let response = result.unwrap();
1144        assert_eq!(response.status(), 201);
1145
1146        let body = to_bytes(response.into_body()).await.unwrap();
1147        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1148
1149        assert!(api_response.success);
1150        let data = api_response.data.unwrap();
1151        assert_eq!(data.id, "test-stellar-relayer");
1152        assert_eq!(data.network_type, RelayerNetworkType::Stellar);
1153
1154        // Verify Stellar policies are present in response
1155        assert!(data.policies.is_some());
1156        cleanup_test_env();
1157    }
1158
1159    #[actix_web::test]
1160    async fn test_create_relayer_with_policy_type_mismatch() {
1161        let _lock = ENV_MUTEX.lock().await;
1162        setup_test_env();
1163        let network = create_mock_network();
1164        let signer = create_mock_signer();
1165        let app_state =
1166            create_mock_app_state(None, Some(vec![signer]), Some(vec![network]), None, None).await;
1167
1168        let mut request = create_test_relayer_create_request(
1169            Some("test-mismatch-relayer".to_string()),
1170            "Test Mismatch Relayer",
1171            "test",
1172            "test",
1173            None,
1174        );
1175
1176        // Set network type to EVM but provide Solana policies (should fail)
1177        request.network_type = RelayerNetworkType::Evm;
1178        request.policies = Some(CreateRelayerPolicyRequest::Solana(
1179            RelayerSolanaPolicy::default(),
1180        ));
1181
1182        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1183
1184        assert!(result.is_err());
1185        if let Err(ApiError::BadRequest(msg)) = result {
1186            assert!(msg.contains("Policy type does not match relayer network type"));
1187        } else {
1188            panic!("Expected BadRequest error for policy type mismatch");
1189        }
1190        cleanup_test_env();
1191    }
1192
1193    #[actix_web::test]
1194    async fn test_create_relayer_with_notification() {
1195        let _lock = ENV_MUTEX.lock().await;
1196        setup_test_env();
1197        let network = create_mock_network();
1198        let signer = create_mock_signer();
1199        let notification = create_mock_notification("test-notification".to_string());
1200        let app_state =
1201            create_mock_app_state(None, Some(vec![signer]), Some(vec![network]), None, None).await;
1202
1203        // Add notification manually since create_mock_app_state doesn't handle notifications
1204        app_state
1205            .notification_repository
1206            .create(notification)
1207            .await
1208            .unwrap();
1209
1210        let request = create_test_relayer_create_request(
1211            Some("test-relayer".to_string()),
1212            "Test Relayer",
1213            "test", // Using "test" to match the mock network name
1214            "test", // Using "test" to match the mock signer id
1215            Some("test-notification".to_string()),
1216        );
1217
1218        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1219
1220        assert!(result.is_ok());
1221        let response = result.unwrap();
1222        assert_eq!(response.status(), 201);
1223        let body = to_bytes(response.into_body()).await.unwrap();
1224        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1225
1226        assert!(api_response.success);
1227        let data = api_response.data.unwrap();
1228        assert_eq!(data.notification_id, Some("test-notification".to_string()));
1229        cleanup_test_env();
1230    }
1231
1232    #[actix_web::test]
1233    async fn test_create_relayer_nonexistent_signer() {
1234        let network = create_mock_network();
1235        let app_state = create_mock_app_state(None, None, Some(vec![network]), None, None).await;
1236
1237        let request = create_test_relayer_create_request(
1238            Some("test-relayer".to_string()),
1239            "Test Relayer",
1240            "test", // Using "test" to match the mock network name
1241            "nonexistent-signer",
1242            None,
1243        );
1244
1245        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1246
1247        assert!(result.is_err());
1248        if let Err(ApiError::NotFound(msg)) = result {
1249            assert!(msg.contains("Signer with ID nonexistent-signer not found"));
1250        } else {
1251            panic!("Expected NotFound error for nonexistent signer");
1252        }
1253    }
1254
1255    #[actix_web::test]
1256    async fn test_create_relayer_nonexistent_network() {
1257        let signer = create_mock_signer();
1258        let app_state = create_mock_app_state(None, Some(vec![signer]), None, None, None).await;
1259
1260        let request = create_test_relayer_create_request(
1261            Some("test-relayer".to_string()),
1262            "Test Relayer",
1263            "nonexistent-network",
1264            "test", // Using "test" to match the mock signer id
1265            None,
1266        );
1267
1268        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1269
1270        assert!(result.is_err());
1271        if let Err(ApiError::BadRequest(msg)) = result {
1272            assert!(msg.contains("Network 'nonexistent-network' not found"));
1273            assert!(msg.contains("network configuration exists"));
1274        } else {
1275            panic!("Expected BadRequest error for nonexistent network");
1276        }
1277    }
1278
1279    #[actix_web::test]
1280    async fn test_create_relayer_signer_already_in_use() {
1281        let network = create_mock_network();
1282        let signer = create_mock_signer();
1283        let mut existing_relayer = create_mock_relayer("existing-relayer".to_string(), false);
1284        existing_relayer.signer_id = "test".to_string(); // Match the mock signer id
1285        existing_relayer.network = "test".to_string(); // Match the mock network name
1286        let app_state = create_mock_app_state(
1287            Some(vec![existing_relayer]),
1288            Some(vec![signer]),
1289            Some(vec![network]),
1290            None,
1291            None,
1292        )
1293        .await;
1294
1295        let request = create_test_relayer_create_request(
1296            Some("test-relayer".to_string()),
1297            "Test Relayer",
1298            "test", // Using "test" to match the mock network name
1299            "test", // Using "test" to match the mock signer id
1300            None,
1301        );
1302
1303        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1304
1305        assert!(result.is_err());
1306        if let Err(ApiError::BadRequest(msg)) = result {
1307            assert!(msg.contains("signer 'test' is already in use"));
1308            assert!(msg.contains("relayer 'existing-relayer'"));
1309            assert!(msg.contains("network 'test'"));
1310            assert!(msg.contains("security reasons"));
1311        } else {
1312            panic!("Expected BadRequest error for signer already in use");
1313        }
1314    }
1315
1316    #[actix_web::test]
1317    async fn test_create_relayer_nonexistent_notification() {
1318        let network = create_mock_network();
1319        let signer = create_mock_signer();
1320        let app_state =
1321            create_mock_app_state(None, Some(vec![signer]), Some(vec![network]), None, None).await;
1322
1323        let request = create_test_relayer_create_request(
1324            Some("test-relayer".to_string()),
1325            "Test Relayer",
1326            "test", // Using "test" to match the mock network name
1327            "test", // Using "test" to match the mock signer id
1328            Some("nonexistent-notification".to_string()),
1329        );
1330
1331        let result = create_relayer(request, actix_web::web::ThinData(app_state)).await;
1332
1333        assert!(result.is_err());
1334        if let Err(ApiError::NotFound(msg)) = result {
1335            assert!(msg.contains("Notification with ID 'nonexistent-notification' not found"));
1336        } else {
1337            panic!("Expected NotFound error for nonexistent notification");
1338        }
1339    }
1340
1341    // LIST RELAYERS TESTS
1342
1343    #[actix_web::test]
1344    async fn test_list_relayers_success() {
1345        let relayer1 = create_mock_relayer("relayer-1".to_string(), false);
1346        let relayer2 = create_mock_relayer("relayer-2".to_string(), false);
1347        let app_state =
1348            create_mock_app_state(Some(vec![relayer1, relayer2]), None, None, None, None).await;
1349
1350        let query = PaginationQuery {
1351            page: 1,
1352            per_page: 10,
1353        };
1354
1355        let result = list_relayers(query, actix_web::web::ThinData(app_state)).await;
1356
1357        assert!(result.is_ok());
1358        let response = result.unwrap();
1359        assert_eq!(response.status(), 200);
1360
1361        let body = to_bytes(response.into_body()).await.unwrap();
1362        let api_response: ApiResponse<Vec<RelayerResponse>> =
1363            serde_json::from_slice(&body).unwrap();
1364
1365        assert!(api_response.success);
1366        let data = api_response.data.unwrap();
1367        assert_eq!(data.len(), 2);
1368    }
1369
1370    #[actix_web::test]
1371    async fn test_list_relayers_empty() {
1372        let app_state = create_mock_app_state(None, None, None, None, None).await;
1373
1374        let query = PaginationQuery {
1375            page: 1,
1376            per_page: 10,
1377        };
1378
1379        let result = list_relayers(query, actix_web::web::ThinData(app_state)).await;
1380
1381        assert!(result.is_ok());
1382        let response = result.unwrap();
1383        assert_eq!(response.status(), 200);
1384
1385        let body = to_bytes(response.into_body()).await.unwrap();
1386        let api_response: ApiResponse<Vec<RelayerResponse>> =
1387            serde_json::from_slice(&body).unwrap();
1388
1389        assert!(api_response.success);
1390        let data = api_response.data.unwrap();
1391        assert_eq!(data.len(), 0);
1392    }
1393
1394    // GET RELAYER TESTS
1395
1396    #[actix_web::test]
1397    async fn test_get_relayer_success() {
1398        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1399        let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1400
1401        let result = get_relayer(
1402            "test-relayer".to_string(),
1403            actix_web::web::ThinData(app_state),
1404        )
1405        .await;
1406
1407        assert!(result.is_ok());
1408        let response = result.unwrap();
1409        assert_eq!(response.status(), 200);
1410
1411        let body = to_bytes(response.into_body()).await.unwrap();
1412        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1413
1414        assert!(api_response.success);
1415        let data = api_response.data.unwrap();
1416        assert_eq!(data.id, "test-relayer");
1417        assert_eq!(data.name, "Relayer test-relayer"); // Mock utility creates name as "Relayer {id}"
1418    }
1419
1420    #[actix_web::test]
1421    async fn test_get_relayer_not_found() {
1422        let app_state = create_mock_app_state(None, None, None, None, None).await;
1423
1424        let result = get_relayer(
1425            "nonexistent".to_string(),
1426            actix_web::web::ThinData(app_state),
1427        )
1428        .await;
1429
1430        assert!(result.is_err());
1431        if let Err(ApiError::NotFound(msg)) = result {
1432            assert!(msg.contains("Relayer with ID nonexistent not found"));
1433        } else {
1434            panic!("Expected NotFound error");
1435        }
1436    }
1437
1438    // UPDATE RELAYER TESTS
1439
1440    #[actix_web::test]
1441    async fn test_update_relayer_success() {
1442        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1443        let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1444
1445        let patch = serde_json::json!({
1446            "name": "Updated Relayer Name",
1447            "paused": true
1448        });
1449
1450        let result = update_relayer(
1451            "test-relayer".to_string(),
1452            patch,
1453            actix_web::web::ThinData(app_state),
1454        )
1455        .await;
1456
1457        assert!(result.is_ok());
1458        let response = result.unwrap();
1459        assert_eq!(response.status(), 200);
1460
1461        let body = to_bytes(response.into_body()).await.unwrap();
1462        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1463
1464        assert!(api_response.success);
1465        let data = api_response.data.unwrap();
1466        assert_eq!(data.name, "Updated Relayer Name");
1467        assert!(data.paused);
1468    }
1469
1470    #[actix_web::test]
1471    async fn test_update_relayer_system_disabled() {
1472        let mut relayer = create_mock_relayer("disabled-relayer".to_string(), false);
1473        relayer.system_disabled = true;
1474        let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1475
1476        let patch = serde_json::json!({
1477            "name": "Updated Name"
1478        });
1479
1480        let result = update_relayer(
1481            "disabled-relayer".to_string(),
1482            patch,
1483            actix_web::web::ThinData(app_state),
1484        )
1485        .await;
1486
1487        assert!(result.is_err());
1488        if let Err(ApiError::BadRequest(msg)) = result {
1489            assert!(msg.contains("Relayer is disabled"));
1490        } else {
1491            panic!("Expected BadRequest error for disabled relayer");
1492        }
1493    }
1494
1495    #[actix_web::test]
1496    async fn test_update_relayer_invalid_patch() {
1497        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1498        let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1499
1500        let patch = serde_json::json!({
1501            "invalid_field": "value"
1502        });
1503
1504        let result = update_relayer(
1505            "test-relayer".to_string(),
1506            patch,
1507            actix_web::web::ThinData(app_state),
1508        )
1509        .await;
1510
1511        assert!(result.is_err());
1512        if let Err(ApiError::BadRequest(msg)) = result {
1513            assert!(msg.contains("Invalid update request"));
1514        } else {
1515            panic!("Expected BadRequest error for invalid patch");
1516        }
1517    }
1518
1519    #[actix_web::test]
1520    async fn test_update_relayer_nonexistent() {
1521        let app_state = create_mock_app_state(None, None, None, None, None).await;
1522
1523        let patch = serde_json::json!({
1524            "name": "Updated Name"
1525        });
1526
1527        let result = update_relayer(
1528            "nonexistent-relayer".to_string(),
1529            patch,
1530            actix_web::web::ThinData(app_state),
1531        )
1532        .await;
1533
1534        assert!(result.is_err());
1535        if let Err(ApiError::NotFound(msg)) = result {
1536            assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
1537        } else {
1538            panic!("Expected NotFound error for nonexistent relayer");
1539        }
1540    }
1541
1542    #[actix_web::test]
1543    async fn test_update_relayer_set_evm_policies() {
1544        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1545        let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1546
1547        let patch = serde_json::json!({
1548            "policies": {
1549                "gas_price_cap": 50000000000u64,
1550                "min_balance": 1000000000000000000u64,
1551                "eip1559_pricing": true,
1552                "private_transactions": false,
1553                "gas_limit_estimation": true,
1554                "whitelist_receivers": ["0x1234567890123456789012345678901234567890"]
1555            }
1556        });
1557
1558        let result = update_relayer(
1559            "test-relayer".to_string(),
1560            patch,
1561            actix_web::web::ThinData(app_state),
1562        )
1563        .await;
1564
1565        assert!(result.is_ok());
1566        let response = result.unwrap();
1567        assert_eq!(response.status(), 200);
1568
1569        let body = to_bytes(response.into_body()).await.unwrap();
1570        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1571
1572        assert!(api_response.success);
1573        let data = api_response.data.unwrap();
1574
1575        // For now, just verify that the policies field exists
1576        // The policy validation can be added once we understand the correct structure
1577        assert!(data.policies.is_some());
1578    }
1579
1580    #[actix_web::test]
1581    async fn test_update_relayer_partial_policy_update() {
1582        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1583        let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1584
1585        // First update with some policies
1586        let patch1 = serde_json::json!({
1587            "policies": {
1588                "gas_price_cap": 30000000000u64,
1589                "min_balance": 500000000000000000u64,
1590                "eip1559_pricing": false
1591            }
1592        });
1593
1594        let result1 = update_relayer(
1595            "test-relayer".to_string(),
1596            patch1,
1597            actix_web::web::ThinData(app_state),
1598        )
1599        .await;
1600
1601        assert!(result1.is_ok());
1602
1603        // Create fresh app state for second update test
1604        let relayer2 = create_mock_relayer("test-relayer".to_string(), false);
1605        let app_state2 = create_mock_app_state(Some(vec![relayer2]), None, None, None, None).await;
1606
1607        // Second update with only gas_price_cap change
1608        let patch2 = serde_json::json!({
1609            "policies": {
1610                "gas_price_cap": 60000000000u64
1611            }
1612        });
1613
1614        let result2 = update_relayer(
1615            "test-relayer".to_string(),
1616            patch2,
1617            actix_web::web::ThinData(app_state2),
1618        )
1619        .await;
1620
1621        assert!(result2.is_ok());
1622        let response = result2.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
1631        // Just verify policies exist for now
1632        assert!(data.policies.is_some());
1633    }
1634
1635    #[actix_web::test]
1636    async fn test_update_relayer_unset_notification() {
1637        let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
1638        relayer.notification_id = Some("test-notification".to_string());
1639        let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1640
1641        let patch = serde_json::json!({
1642            "notification_id": null
1643        });
1644
1645        let result = update_relayer(
1646            "test-relayer".to_string(),
1647            patch,
1648            actix_web::web::ThinData(app_state),
1649        )
1650        .await;
1651
1652        assert!(result.is_ok());
1653        let response = result.unwrap();
1654        assert_eq!(response.status(), 200);
1655
1656        let body = to_bytes(response.into_body()).await.unwrap();
1657        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1658
1659        assert!(api_response.success);
1660        let data = api_response.data.unwrap();
1661        assert_eq!(data.notification_id, None);
1662    }
1663
1664    #[actix_web::test]
1665    async fn test_update_relayer_unset_custom_rpc_urls() {
1666        let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
1667        relayer.custom_rpc_urls = Some(vec![crate::models::RpcConfig {
1668            url: "https://custom-rpc.example.com".to_string(),
1669            weight: 50,
1670        }]);
1671        let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1672
1673        let patch = serde_json::json!({
1674            "custom_rpc_urls": null
1675        });
1676
1677        let result = update_relayer(
1678            "test-relayer".to_string(),
1679            patch,
1680            actix_web::web::ThinData(app_state),
1681        )
1682        .await;
1683
1684        assert!(result.is_ok());
1685        let response = result.unwrap();
1686        assert_eq!(response.status(), 200);
1687
1688        let body = to_bytes(response.into_body()).await.unwrap();
1689        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1690
1691        assert!(api_response.success);
1692        let data = api_response.data.unwrap();
1693        assert_eq!(data.custom_rpc_urls, None);
1694    }
1695
1696    #[actix_web::test]
1697    async fn test_update_relayer_set_custom_rpc_urls() {
1698        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1699        let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1700
1701        let patch = serde_json::json!({
1702            "custom_rpc_urls": [
1703                {
1704                    "url": "https://rpc1.example.com",
1705                    "weight": 80
1706                },
1707                {
1708                    "url": "https://rpc2.example.com",
1709                    "weight": 60
1710                }
1711            ]
1712        });
1713
1714        let result = update_relayer(
1715            "test-relayer".to_string(),
1716            patch,
1717            actix_web::web::ThinData(app_state),
1718        )
1719        .await;
1720
1721        assert!(result.is_ok());
1722        let response = result.unwrap();
1723        assert_eq!(response.status(), 200);
1724
1725        let body = to_bytes(response.into_body()).await.unwrap();
1726        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1727
1728        assert!(api_response.success);
1729        let data = api_response.data.unwrap();
1730
1731        assert!(data.custom_rpc_urls.is_some());
1732        let rpc_urls = data.custom_rpc_urls.unwrap();
1733        assert_eq!(rpc_urls.len(), 2);
1734        assert_eq!(rpc_urls[0].url, "https://rpc1.example.com");
1735        assert_eq!(rpc_urls[0].weight, 80);
1736        assert_eq!(rpc_urls[1].url, "https://rpc2.example.com");
1737        assert_eq!(rpc_urls[1].weight, 60);
1738    }
1739
1740    #[actix_web::test]
1741    async fn test_update_relayer_clear_policies() {
1742        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1743        let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1744
1745        let patch = serde_json::json!({
1746            "policies": null
1747        });
1748
1749        let result = update_relayer(
1750            "test-relayer".to_string(),
1751            patch,
1752            actix_web::web::ThinData(app_state),
1753        )
1754        .await;
1755
1756        assert!(result.is_ok());
1757        let response = result.unwrap();
1758        assert_eq!(response.status(), 200);
1759
1760        let body = to_bytes(response.into_body()).await.unwrap();
1761        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1762
1763        assert!(api_response.success);
1764        let data = api_response.data.unwrap();
1765        assert_eq!(data.policies, None);
1766    }
1767
1768    #[actix_web::test]
1769    async fn test_update_relayer_invalid_policy_structure() {
1770        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1771        let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1772
1773        let patch = serde_json::json!({
1774            "policies": {
1775                "invalid_field_name": "some_value"
1776            }
1777        });
1778
1779        let result = update_relayer(
1780            "test-relayer".to_string(),
1781            patch,
1782            actix_web::web::ThinData(app_state),
1783        )
1784        .await;
1785
1786        assert!(result.is_err());
1787        if let Err(ApiError::BadRequest(msg)) = result {
1788            assert!(msg.contains("Invalid policy"));
1789        } else {
1790            panic!("Expected BadRequest error for invalid policy structure");
1791        }
1792    }
1793
1794    #[actix_web::test]
1795    async fn test_update_relayer_invalid_evm_policy_values() {
1796        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1797        let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1798
1799        let patch = serde_json::json!({
1800            "policies": {
1801                "gas_price_cap": "invalid_number",
1802                "min_balance": -1
1803            }
1804        });
1805
1806        let result = update_relayer(
1807            "test-relayer".to_string(),
1808            patch,
1809            actix_web::web::ThinData(app_state),
1810        )
1811        .await;
1812
1813        assert!(result.is_err());
1814        if let Err(ApiError::BadRequest(msg)) = result {
1815            assert!(msg.contains("Invalid policy") || msg.contains("Invalid update request"));
1816        } else {
1817            panic!("Expected BadRequest error for invalid policy values");
1818        }
1819    }
1820
1821    #[actix_web::test]
1822    async fn test_update_relayer_multiple_fields_at_once() {
1823        let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
1824        relayer.notification_id = Some("old-notification".to_string());
1825        let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1826
1827        let patch = serde_json::json!({
1828            "name": "Multi-Update Relayer",
1829            "paused": true,
1830            "notification_id": null,
1831            "policies": {
1832                "gas_price_cap": 40000000000u64,
1833                "eip1559_pricing": true
1834            },
1835            "custom_rpc_urls": [
1836                {
1837                    "url": "https://new-rpc.example.com",
1838                    "weight": 90
1839                }
1840            ]
1841        });
1842
1843        let result = update_relayer(
1844            "test-relayer".to_string(),
1845            patch,
1846            actix_web::web::ThinData(app_state),
1847        )
1848        .await;
1849
1850        assert!(result.is_ok());
1851        let response = result.unwrap();
1852        assert_eq!(response.status(), 200);
1853
1854        let body = to_bytes(response.into_body()).await.unwrap();
1855        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1856
1857        assert!(api_response.success);
1858        let data = api_response.data.unwrap();
1859
1860        // Verify all fields were updated correctly
1861        assert_eq!(data.name, "Multi-Update Relayer");
1862        assert!(data.paused);
1863        assert_eq!(data.notification_id, None);
1864
1865        // Verify policies and RPC URLs were set
1866        assert!(data.policies.is_some());
1867        assert!(data.custom_rpc_urls.is_some());
1868        let rpc_urls = data.custom_rpc_urls.unwrap();
1869        assert_eq!(rpc_urls.len(), 1);
1870        assert_eq!(rpc_urls[0].url, "https://new-rpc.example.com");
1871        assert_eq!(rpc_urls[0].weight, 90);
1872    }
1873
1874    #[actix_web::test]
1875    async fn test_update_relayer_solana_policies() {
1876        use crate::models::{
1877            NetworkType, RelayerNetworkPolicy, RelayerSolanaPolicy, SolanaFeePaymentStrategy,
1878        };
1879
1880        // Create a Solana relayer (not the default EVM one)
1881        let mut solana_relayer = create_mock_relayer("test-solana-relayer".to_string(), false);
1882        solana_relayer.network_type = NetworkType::Solana;
1883        solana_relayer.policies = RelayerNetworkPolicy::Solana(RelayerSolanaPolicy::default());
1884
1885        let app_state =
1886            create_mock_app_state(Some(vec![solana_relayer]), None, None, None, None).await;
1887
1888        let patch = serde_json::json!({
1889            "policies": {
1890                "fee_payment_strategy": "user",
1891                "min_balance": 2000000,
1892                "max_signatures": 5,
1893                "max_tx_data_size": 800,
1894                "max_allowed_fee_lamports": 25000,
1895                "fee_margin_percentage": 15.0
1896            }
1897        });
1898
1899        let result = update_relayer(
1900            "test-solana-relayer".to_string(),
1901            patch,
1902            actix_web::web::ThinData(app_state),
1903        )
1904        .await;
1905
1906        assert!(result.is_ok());
1907        let response = result.unwrap();
1908        assert_eq!(response.status(), 200);
1909
1910        let body = to_bytes(response.into_body()).await.unwrap();
1911        let api_response: ApiResponse<RelayerResponse> = serde_json::from_slice(&body).unwrap();
1912
1913        assert!(api_response.success);
1914        let data = api_response.data.unwrap();
1915
1916        // Verify Solana policies are present and correctly updated
1917        assert!(data.policies.is_some());
1918        let policies = data.policies.unwrap();
1919        if let RelayerNetworkPolicyResponse::Solana(solana_policy) = policies {
1920            assert_eq!(
1921                solana_policy.fee_payment_strategy,
1922                Some(SolanaFeePaymentStrategy::User)
1923            );
1924            assert_eq!(solana_policy.min_balance, 2000000);
1925            assert_eq!(solana_policy.max_signatures, Some(5));
1926            assert_eq!(solana_policy.max_tx_data_size, 800);
1927            assert_eq!(solana_policy.max_allowed_fee_lamports, Some(25000));
1928            assert_eq!(solana_policy.fee_margin_percentage, Some(15.0));
1929        } else {
1930            panic!("Expected Solana policies in response");
1931        }
1932    }
1933
1934    // DELETE RELAYER TESTS
1935
1936    #[actix_web::test]
1937    async fn test_delete_relayer_success() {
1938        let relayer = create_mock_relayer("test-relayer".to_string(), false);
1939        let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
1940
1941        let result = delete_relayer(
1942            "test-relayer".to_string(),
1943            actix_web::web::ThinData(app_state),
1944        )
1945        .await;
1946
1947        assert!(result.is_ok());
1948        let response = result.unwrap();
1949        assert_eq!(response.status(), 200);
1950
1951        let body = to_bytes(response.into_body()).await.unwrap();
1952        let api_response: ApiResponse<String> = serde_json::from_slice(&body).unwrap();
1953
1954        assert!(api_response.success);
1955        let data = api_response.data.unwrap();
1956        assert!(data.contains("Relayer deleted successfully"));
1957    }
1958
1959    #[actix_web::test]
1960    async fn test_delete_relayer_with_transactions() {
1961        let relayer = create_mock_relayer("relayer-with-tx".to_string(), false);
1962        let mut transaction = create_mock_transaction();
1963        transaction.id = "test-tx".to_string();
1964        transaction.relayer_id = "relayer-with-tx".to_string();
1965        let app_state = create_mock_app_state(
1966            Some(vec![relayer]),
1967            None,
1968            None,
1969            None,
1970            Some(vec![transaction]),
1971        )
1972        .await;
1973
1974        let result = delete_relayer(
1975            "relayer-with-tx".to_string(),
1976            actix_web::web::ThinData(app_state),
1977        )
1978        .await;
1979
1980        assert!(result.is_err());
1981        if let Err(ApiError::BadRequest(msg)) = result {
1982            assert!(msg.contains("Cannot delete relayer 'relayer-with-tx'"));
1983            assert!(msg.contains("has 1 transaction(s)"));
1984            assert!(msg.contains("wait for all transactions to complete"));
1985        } else {
1986            panic!("Expected BadRequest error for relayer with transactions");
1987        }
1988    }
1989
1990    #[actix_web::test]
1991    async fn test_delete_relayer_nonexistent() {
1992        let app_state = create_mock_app_state(None, None, None, None, None).await;
1993
1994        let result = delete_relayer(
1995            "nonexistent-relayer".to_string(),
1996            actix_web::web::ThinData(app_state),
1997        )
1998        .await;
1999
2000        assert!(result.is_err());
2001        if let Err(ApiError::NotFound(msg)) = result {
2002            assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
2003        } else {
2004            panic!("Expected NotFound error for nonexistent relayer");
2005        }
2006    }
2007
2008    #[actix_web::test]
2009    async fn test_sign_transaction_success() {
2010        let _lock = ENV_MUTEX.lock().await;
2011        setup_test_env();
2012        let network = create_mock_stellar_network();
2013        let signer = create_mock_signer();
2014        let mut relayer = create_mock_relayer("test-relayer".to_string(), false);
2015        relayer.network_type = NetworkType::Stellar;
2016        let app_state = create_mock_app_state(
2017            Some(vec![relayer]),
2018            Some(vec![signer]),
2019            Some(vec![network]),
2020            None,
2021            None,
2022        )
2023        .await;
2024
2025        let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2026            unsigned_xdr: "test-unsigned-xdr".to_string(),
2027        });
2028
2029        let result = sign_transaction(
2030            "test-relayer".to_string(),
2031            request,
2032            actix_web::web::ThinData(app_state),
2033        )
2034        .await;
2035
2036        // The actual signing will fail in the mock environment, but we can test that
2037        // the function is callable with generics and processes the request
2038        assert!(result.is_err());
2039        cleanup_test_env();
2040    }
2041
2042    #[actix_web::test]
2043    async fn test_sign_transaction_relayer_not_found() {
2044        let app_state = create_mock_app_state(None, None, None, None, None).await;
2045
2046        let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2047            unsigned_xdr: "test-unsigned-xdr".to_string(),
2048        });
2049
2050        let result = sign_transaction(
2051            "nonexistent-relayer".to_string(),
2052            request,
2053            actix_web::web::ThinData(app_state),
2054        )
2055        .await;
2056
2057        assert!(result.is_err());
2058        if let Err(ApiError::NotFound(msg)) = result {
2059            assert!(msg.contains("Relayer with ID nonexistent-relayer not found"));
2060        } else {
2061            panic!("Expected NotFound error for nonexistent relayer");
2062        }
2063    }
2064
2065    #[actix_web::test]
2066    async fn test_sign_transaction_relayer_disabled() {
2067        let mut relayer = create_mock_relayer("disabled-relayer".to_string(), false);
2068        relayer.paused = true;
2069        let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
2070
2071        let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2072            unsigned_xdr: "test-unsigned-xdr".to_string(),
2073        });
2074
2075        let result = sign_transaction(
2076            "disabled-relayer".to_string(),
2077            request,
2078            actix_web::web::ThinData(app_state),
2079        )
2080        .await;
2081
2082        assert!(result.is_err());
2083        if let Err(ApiError::ForbiddenError(msg)) = result {
2084            assert!(msg.contains("Relayer paused"));
2085        } else {
2086            panic!("Expected ForbiddenError for paused relayer");
2087        }
2088    }
2089
2090    #[actix_web::test]
2091    async fn test_sign_transaction_system_disabled() {
2092        let mut relayer = create_mock_relayer("system-disabled-relayer".to_string(), false);
2093        relayer.system_disabled = true;
2094        let app_state = create_mock_app_state(Some(vec![relayer]), None, None, None, None).await;
2095
2096        let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
2097            unsigned_xdr: "test-unsigned-xdr".to_string(),
2098        });
2099
2100        let result = sign_transaction(
2101            "system-disabled-relayer".to_string(),
2102            request,
2103            actix_web::web::ThinData(app_state),
2104        )
2105        .await;
2106
2107        assert!(result.is_err());
2108        if let Err(ApiError::ForbiddenError(msg)) = result {
2109            assert!(msg.contains("Relayer disabled"));
2110        } else {
2111            panic!("Expected ForbiddenError for system disabled relayer");
2112        }
2113    }
2114}