openzeppelin_relayer/api/controllers/
relayer.rs

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