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