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//! - Submitting transactions
7//! - Signing messages
8//! - JSON-RPC proxy
9use crate::{
10    domain::{
11        get_network_relayer, get_network_relayer_by_model, get_relayer_by_id,
12        get_relayer_transaction_by_model, get_transaction_by_id as get_tx_by_id, Relayer,
13        RelayerUpdateRequest, SignDataRequest, SignDataResponse, SignTypedDataRequest, Transaction,
14    },
15    jobs::JobProducer,
16    models::{
17        convert_to_internal_rpc_request, ApiError, ApiResponse, AppState,
18        NetworkTransactionRequest, NetworkType, PaginationMeta, PaginationQuery, RelayerResponse,
19        TransactionResponse,
20    },
21    repositories::{RelayerRepository, Repository, TransactionRepository},
22};
23use actix_web::{web, HttpResponse};
24use eyre::Result;
25
26/// Lists all relayers with pagination support.
27///
28/// # Arguments
29///
30/// * `query` - The pagination query parameters.
31/// * `state` - The application state containing the relayer repository.
32///
33/// # Returns
34///
35/// A paginated list of relayers.
36pub async fn list_relayers(
37    query: PaginationQuery,
38    state: web::ThinData<AppState<JobProducer>>,
39) -> Result<HttpResponse, ApiError> {
40    let relayers = state.relayer_repository.list_paginated(query).await?;
41
42    let mapped_relayers: Vec<RelayerResponse> =
43        relayers.items.into_iter().map(|r| r.into()).collect();
44
45    Ok(HttpResponse::Ok().json(ApiResponse::paginated(
46        mapped_relayers,
47        PaginationMeta {
48            total_items: relayers.total,
49            current_page: relayers.page,
50            per_page: relayers.per_page,
51        },
52    )))
53}
54
55/// Retrieves details of a specific relayer by its ID.
56///
57/// # Arguments
58///
59/// * `relayer_id` - The ID of the relayer to retrieve.
60/// * `state` - The application state containing the relayer repository.
61///
62/// # Returns
63///
64/// The details of the specified relayer.
65pub async fn get_relayer(
66    relayer_id: String,
67    state: web::ThinData<AppState<JobProducer>>,
68) -> Result<HttpResponse, ApiError> {
69    let relayer = get_relayer_by_id(relayer_id, &state).await?;
70
71    let relayer_response: RelayerResponse = relayer.into();
72
73    Ok(HttpResponse::Ok().json(ApiResponse::success(relayer_response)))
74}
75
76/// Updates a relayer's information.
77///
78/// # Arguments
79///
80/// * `relayer_id` - The ID of the relayer to update.
81/// * `update_req` - The update request containing new relayer data.
82/// * `state` - The application state containing the relayer repository.
83///
84/// # Returns
85///
86/// The updated relayer information.
87pub async fn update_relayer(
88    relayer_id: String,
89    update_req: RelayerUpdateRequest,
90    state: web::ThinData<AppState<JobProducer>>,
91) -> Result<HttpResponse, ApiError> {
92    let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
93
94    if relayer.system_disabled || (relayer.paused && update_req.paused != Some(false)) {
95        let error_message = if relayer.system_disabled {
96            "Relayer is disabled"
97        } else {
98            "Relayer is paused"
99        };
100        return Err(ApiError::BadRequest(error_message.to_string()));
101    }
102
103    let updated_relayer = state
104        .relayer_repository
105        .partial_update(relayer_id.clone(), update_req)
106        .await?;
107
108    let relayer_response: RelayerResponse = updated_relayer.into();
109
110    Ok(HttpResponse::Ok().json(ApiResponse::success(relayer_response)))
111}
112
113/// Retrieves the status of a specific relayer.
114///
115/// # Arguments
116///
117/// * `relayer_id` - The ID of the relayer to check status for.
118/// * `state` - The application state containing the relayer repository.
119///
120/// # Returns
121///
122/// The status of the specified relayer.
123pub async fn get_relayer_status(
124    relayer_id: String,
125    state: web::ThinData<AppState<JobProducer>>,
126) -> Result<HttpResponse, ApiError> {
127    let relayer = get_network_relayer(relayer_id, &state).await?;
128
129    let status = relayer.get_status().await?;
130
131    Ok(HttpResponse::Ok().json(ApiResponse::success(status)))
132}
133
134/// Retrieves the balance of a specific relayer.
135///
136/// # Arguments
137///
138/// * `relayer_id` - The ID of the relayer to check balance for.
139/// * `state` - The application state containing the relayer repository.
140///
141/// # Returns
142///
143/// The balance of the specified relayer.
144pub async fn get_relayer_balance(
145    relayer_id: String,
146    state: web::ThinData<AppState<JobProducer>>,
147) -> Result<HttpResponse, ApiError> {
148    let relayer = get_network_relayer(relayer_id, &state).await?;
149
150    let result = relayer.get_balance().await?;
151
152    Ok(HttpResponse::Ok().json(ApiResponse::success(result)))
153}
154
155/// Sends a transaction through a specified relayer.
156///
157/// # Arguments
158///
159/// * `relayer_id` - The ID of the relayer to send the transaction through.
160/// * `request` - The transaction request data.
161/// * `state` - The application state containing the relayer repository.
162///
163/// # Returns
164///
165/// The response of the transaction processing.
166pub async fn send_transaction(
167    relayer_id: String,
168    request: serde_json::Value,
169    state: web::ThinData<AppState<JobProducer>>,
170) -> Result<HttpResponse, ApiError> {
171    let relayer_repo_model = get_relayer_by_id(relayer_id, &state).await?;
172    relayer_repo_model.validate_active_state()?;
173
174    let relayer = get_network_relayer(relayer_repo_model.id.clone(), &state).await?;
175
176    let tx_request: NetworkTransactionRequest =
177        NetworkTransactionRequest::from_json(&relayer_repo_model.network_type, request.clone())?;
178
179    tx_request.validate(&relayer_repo_model)?;
180
181    let transaction = relayer.process_transaction_request(tx_request).await?;
182
183    let transaction_response: TransactionResponse = transaction.into();
184
185    Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
186}
187
188/// Retrieves a transaction by its ID for a specific relayer.
189///
190/// # Arguments
191///
192/// * `relayer_id` - The ID of the relayer.
193/// * `transaction_id` - The ID of the transaction to retrieve.
194/// * `state` - The application state containing the transaction repository.
195///
196/// # Returns
197///
198/// The details of the specified transaction.
199pub async fn get_transaction_by_id(
200    relayer_id: String,
201    transaction_id: String,
202    state: web::ThinData<AppState<JobProducer>>,
203) -> Result<HttpResponse, ApiError> {
204    if relayer_id.is_empty() || transaction_id.is_empty() {
205        return Ok(HttpResponse::Ok().json(ApiResponse::<()>::error(
206            "Invalid relayer or transaction ID".to_string(),
207        )));
208    }
209    // validation purpose only, checks if relayer exists
210    get_relayer_by_id(relayer_id, &state).await?;
211
212    let transaction = get_tx_by_id(transaction_id, &state).await?;
213
214    let transaction_response: TransactionResponse = transaction.into();
215
216    Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
217}
218
219/// Retrieves a transaction by its nonce for a specific relayer.
220///
221/// # Arguments
222///
223/// * `relayer_id` - The ID of the relayer.
224/// * `nonce` - The nonce of the transaction to retrieve.
225/// * `state` - The application state containing the transaction repository.
226///
227/// # Returns
228///
229/// The details of the specified transaction.
230pub async fn get_transaction_by_nonce(
231    relayer_id: String,
232    nonce: u64,
233    state: web::ThinData<AppState<JobProducer>>,
234) -> Result<HttpResponse, ApiError> {
235    let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
236
237    // get by nonce is only supported for EVM network
238    if relayer.network_type != NetworkType::Evm {
239        return Err(ApiError::NotSupported(
240            "Nonce lookup only supported for EVM networks".into(),
241        ));
242    }
243
244    let transaction = state
245        .transaction_repository
246        .find_by_nonce(&relayer_id, nonce)
247        .await?
248        .ok_or_else(|| ApiError::NotFound(format!("Transaction with nonce {} not found", nonce)))?;
249
250    let transaction_response: TransactionResponse = transaction.into();
251
252    Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
253}
254
255/// Lists all transactions for a specific relayer with pagination support.
256///
257/// # Arguments
258///
259/// * `relayer_id` - The ID of the relayer.
260/// * `query` - The pagination query parameters.
261/// * `state` - The application state containing the transaction repository.
262///
263/// # Returns
264///
265/// A paginated list of transactions
266pub async fn list_transactions(
267    relayer_id: String,
268    query: PaginationQuery,
269    state: web::ThinData<AppState<JobProducer>>,
270) -> Result<HttpResponse, ApiError> {
271    get_relayer_by_id(relayer_id.clone(), &state).await?;
272
273    let transactions = state
274        .transaction_repository
275        .find_by_relayer_id(&relayer_id, query)
276        .await?;
277
278    let transaction_response_list: Vec<TransactionResponse> =
279        transactions.items.into_iter().map(|t| t.into()).collect();
280
281    Ok(HttpResponse::Ok().json(ApiResponse::paginated(
282        transaction_response_list,
283        PaginationMeta {
284            total_items: transactions.total,
285            current_page: transactions.page,
286            per_page: transactions.per_page,
287        },
288    )))
289}
290
291/// Deletes all pending transactions for a specific relayer.
292///
293/// # Arguments
294///
295/// * `relayer_id` - The ID of the relayer.
296/// * `state` - The application state containing the relayer repository.
297///
298/// # Returns
299///
300/// A success response with details about cancelled and failed transactions.
301pub async fn delete_pending_transactions(
302    relayer_id: String,
303    state: web::ThinData<AppState<JobProducer>>,
304) -> Result<HttpResponse, ApiError> {
305    let relayer = get_relayer_by_id(relayer_id, &state).await?;
306    relayer.validate_active_state()?;
307    let network_relayer = get_network_relayer_by_model(relayer.clone(), &state).await?;
308
309    let result = network_relayer.delete_pending_transactions().await?;
310
311    Ok(HttpResponse::Ok().json(ApiResponse::success(result)))
312}
313
314/// Cancels a specific transaction for a relayer.
315///
316/// # Arguments
317///
318/// * `relayer_id` - The ID of the relayer.
319/// * `transaction_id` - The ID of the transaction to cancel.
320/// * `state` - The application state containing the transaction repository.
321///
322/// # Returns
323///
324/// The details of the canceled transaction.
325pub async fn cancel_transaction(
326    relayer_id: String,
327    transaction_id: String,
328    state: web::ThinData<AppState<JobProducer>>,
329) -> Result<HttpResponse, ApiError> {
330    let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
331    relayer.validate_active_state()?;
332
333    let relayer_transaction = get_relayer_transaction_by_model(relayer.clone(), &state).await?;
334
335    let transaction_to_cancel = get_tx_by_id(transaction_id, &state).await?;
336
337    let canceled_transaction = relayer_transaction
338        .cancel_transaction(transaction_to_cancel)
339        .await?;
340
341    let transaction_response: TransactionResponse = canceled_transaction.into();
342
343    Ok(HttpResponse::Ok().json(ApiResponse::success(transaction_response)))
344}
345
346/// Replaces a specific transaction for a relayer.
347///
348/// # Arguments
349///
350/// * `relayer_id` - The ID of the relayer.
351/// * `transaction_id` - The ID of the transaction to replace.
352/// * `request` - The new transaction request data.
353/// * `state` - The application state containing the transaction repository.
354///
355/// # Returns
356///
357/// The details of the replaced transaction.
358pub async fn replace_transaction(
359    relayer_id: String,
360    transaction_id: String,
361    request: serde_json::Value,
362    state: web::ThinData<AppState<JobProducer>>,
363) -> Result<HttpResponse, ApiError> {
364    let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
365    relayer.validate_active_state()?;
366
367    let new_tx_request: NetworkTransactionRequest =
368        NetworkTransactionRequest::from_json(&relayer.network_type, request.clone())?;
369    new_tx_request.validate(&relayer)?;
370
371    let transaction_to_replace = state
372        .transaction_repository
373        .get_by_id(transaction_id)
374        .await?;
375
376    let relayer_transaction = get_relayer_transaction_by_model(relayer.clone(), &state).await?;
377    let replaced_transaction = relayer_transaction
378        .replace_transaction(transaction_to_replace, new_tx_request)
379        .await?;
380
381    Ok(HttpResponse::Ok().json(ApiResponse::success(replaced_transaction)))
382}
383
384/// Signs data using a specific relayer.
385///
386/// # Arguments
387///
388/// * `relayer_id` - The ID of the relayer.
389/// * `request` - The sign data request.
390/// * `state` - The application state containing the relayer repository.
391///
392/// # Returns
393///
394/// The signed data response.
395pub async fn sign_data(
396    relayer_id: String,
397    request: SignDataRequest,
398    state: web::ThinData<AppState<JobProducer>>,
399) -> Result<HttpResponse, ApiError> {
400    let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
401    relayer.validate_active_state()?;
402    let network_relayer = get_network_relayer_by_model(relayer, &state).await?;
403
404    let result = network_relayer.sign_data(request).await?;
405
406    if let SignDataResponse::Evm(sign) = result {
407        Ok(HttpResponse::Ok().json(ApiResponse::success(sign)))
408    } else {
409        Err(ApiError::NotSupported("Sign data not supported".into()))
410    }
411}
412
413/// Signs typed data using a specific relayer.
414///
415/// # Arguments
416///
417/// * `relayer_id` - The ID of the relayer.
418/// * `request` - The sign typed data request.
419/// * `state` - The application state containing the relayer repository.
420///
421/// # Returns
422///
423/// The signed typed data response.
424pub async fn sign_typed_data(
425    relayer_id: String,
426    request: SignTypedDataRequest,
427    state: web::ThinData<AppState<JobProducer>>,
428) -> Result<HttpResponse, ApiError> {
429    let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
430    relayer.validate_active_state()?;
431    let network_relayer = get_network_relayer_by_model(relayer, &state).await?;
432
433    let result = network_relayer.sign_typed_data(request).await?;
434
435    Ok(HttpResponse::Ok().json(ApiResponse::success(result)))
436}
437
438/// Performs a JSON-RPC call through a specific relayer.
439///
440/// # Arguments
441///
442/// * `relayer_id` - The ID of the relayer.
443/// * `request` - The raw JSON-RPC request value.
444/// * `state` - The application state containing the relayer repository.
445///
446/// # Returns
447///
448/// The result of the JSON-RPC call.
449pub async fn relayer_rpc(
450    relayer_id: String,
451    request: serde_json::Value,
452    state: web::ThinData<AppState<JobProducer>>,
453) -> Result<HttpResponse, ApiError> {
454    let relayer = get_relayer_by_id(relayer_id.clone(), &state).await?;
455    relayer.validate_active_state()?;
456    let network_relayer = get_network_relayer_by_model(relayer.clone(), &state).await?;
457
458    let internal_request = convert_to_internal_rpc_request(request, &relayer.network_type)?;
459    let result = network_relayer.rpc(internal_request).await?;
460
461    Ok(HttpResponse::Ok().json(result))
462}