1use crate::{
5 api::controllers::relayer,
6 domain::{SignDataRequest, SignTransactionRequest, SignTypedDataRequest},
7 models::{CreateRelayerRequest, DefaultAppState, PaginationQuery},
8};
9use actix_web::{delete, get, patch, post, put, web, Responder};
10use serde::Deserialize;
11use utoipa::ToSchema;
12
13#[get("/relayers")]
15async fn list_relayers(
16 query: web::Query<PaginationQuery>,
17 data: web::ThinData<DefaultAppState>,
18) -> impl Responder {
19 relayer::list_relayers(query.into_inner(), data).await
20}
21
22#[get("/relayers/{relayer_id}")]
24async fn get_relayer(
25 relayer_id: web::Path<String>,
26 data: web::ThinData<DefaultAppState>,
27) -> impl Responder {
28 relayer::get_relayer(relayer_id.into_inner(), data).await
29}
30
31#[post("/relayers")]
33async fn create_relayer(
34 request: web::Json<CreateRelayerRequest>,
35 data: web::ThinData<DefaultAppState>,
36) -> impl Responder {
37 relayer::create_relayer(request.into_inner(), data).await
38}
39
40#[patch("/relayers/{relayer_id}")]
42async fn update_relayer(
43 relayer_id: web::Path<String>,
44 patch: web::Json<serde_json::Value>,
45 data: web::ThinData<DefaultAppState>,
46) -> impl Responder {
47 relayer::update_relayer(relayer_id.into_inner(), patch.into_inner(), data).await
48}
49
50#[delete("/relayers/{relayer_id}")]
52async fn delete_relayer(
53 relayer_id: web::Path<String>,
54 data: web::ThinData<DefaultAppState>,
55) -> impl Responder {
56 relayer::delete_relayer(relayer_id.into_inner(), data).await
57}
58
59#[get("/relayers/{relayer_id}/status")]
61async fn get_relayer_status(
62 relayer_id: web::Path<String>,
63 data: web::ThinData<DefaultAppState>,
64) -> impl Responder {
65 relayer::get_relayer_status(relayer_id.into_inner(), data).await
66}
67
68#[get("/relayers/{relayer_id}/balance")]
70async fn get_relayer_balance(
71 relayer_id: web::Path<String>,
72 data: web::ThinData<DefaultAppState>,
73) -> impl Responder {
74 relayer::get_relayer_balance(relayer_id.into_inner(), data).await
75}
76
77#[post("/relayers/{relayer_id}/transactions")]
79async fn send_transaction(
80 relayer_id: web::Path<String>,
81 req: web::Json<serde_json::Value>,
82 data: web::ThinData<DefaultAppState>,
83) -> impl Responder {
84 relayer::send_transaction(relayer_id.into_inner(), req.into_inner(), data).await
85}
86
87#[derive(Deserialize, ToSchema)]
88pub struct TransactionPath {
89 relayer_id: String,
90 transaction_id: String,
91}
92
93#[get("/relayers/{relayer_id}/transactions/{transaction_id}")]
95async fn get_transaction_by_id(
96 path: web::Path<TransactionPath>,
97 data: web::ThinData<DefaultAppState>,
98) -> impl Responder {
99 let path = path.into_inner();
100 relayer::get_transaction_by_id(path.relayer_id, path.transaction_id, data).await
101}
102
103#[get("/relayers/{relayer_id}/transactions/by-nonce/{nonce}")]
105async fn get_transaction_by_nonce(
106 params: web::Path<(String, u64)>,
107 data: web::ThinData<DefaultAppState>,
108) -> impl Responder {
109 let params = params.into_inner();
110 relayer::get_transaction_by_nonce(params.0, params.1, data).await
111}
112
113#[get("/relayers/{relayer_id}/transactions")]
115async fn list_transactions(
116 relayer_id: web::Path<String>,
117 query: web::Query<PaginationQuery>,
118 data: web::ThinData<DefaultAppState>,
119) -> impl Responder {
120 relayer::list_transactions(relayer_id.into_inner(), query.into_inner(), data).await
121}
122
123#[delete("/relayers/{relayer_id}/transactions/pending")]
125async fn delete_pending_transactions(
126 relayer_id: web::Path<String>,
127 data: web::ThinData<DefaultAppState>,
128) -> impl Responder {
129 relayer::delete_pending_transactions(relayer_id.into_inner(), data).await
130}
131
132#[delete("/relayers/{relayer_id}/transactions/{transaction_id}")]
134async fn cancel_transaction(
135 path: web::Path<TransactionPath>,
136 data: web::ThinData<DefaultAppState>,
137) -> impl Responder {
138 let path = path.into_inner();
139 relayer::cancel_transaction(path.relayer_id, path.transaction_id, data).await
140}
141
142#[put("/relayers/{relayer_id}/transactions/{transaction_id}")]
144async fn replace_transaction(
145 path: web::Path<TransactionPath>,
146 req: web::Json<serde_json::Value>,
147 data: web::ThinData<DefaultAppState>,
148) -> impl Responder {
149 let path = path.into_inner();
150 relayer::replace_transaction(path.relayer_id, path.transaction_id, req.into_inner(), data).await
151}
152
153#[post("/relayers/{relayer_id}/sign")]
155async fn sign(
156 relayer_id: web::Path<String>,
157 req: web::Json<SignDataRequest>,
158 data: web::ThinData<DefaultAppState>,
159) -> impl Responder {
160 relayer::sign_data(relayer_id.into_inner(), req.into_inner(), data).await
161}
162
163#[post("/relayers/{relayer_id}/sign-typed-data")]
165async fn sign_typed_data(
166 relayer_id: web::Path<String>,
167 req: web::Json<SignTypedDataRequest>,
168 data: web::ThinData<DefaultAppState>,
169) -> impl Responder {
170 relayer::sign_typed_data(relayer_id.into_inner(), req.into_inner(), data).await
171}
172
173#[post("/relayers/{relayer_id}/sign-transaction")]
175async fn sign_transaction(
176 relayer_id: web::Path<String>,
177 req: web::Json<SignTransactionRequest>,
178 data: web::ThinData<DefaultAppState>,
179) -> impl Responder {
180 relayer::sign_transaction(relayer_id.into_inner(), req.into_inner(), data).await
181}
182
183#[post("/relayers/{relayer_id}/rpc")]
185async fn rpc(
186 relayer_id: web::Path<String>,
187 req: web::Json<serde_json::Value>,
188 data: web::ThinData<DefaultAppState>,
189) -> impl Responder {
190 relayer::relayer_rpc(relayer_id.into_inner(), req.into_inner(), data).await
191}
192
193pub fn init(cfg: &mut web::ServiceConfig) {
195 cfg.service(delete_pending_transactions); cfg.service(cancel_transaction); cfg.service(replace_transaction); cfg.service(get_transaction_by_id); cfg.service(get_transaction_by_nonce); cfg.service(send_transaction); cfg.service(list_transactions); cfg.service(get_relayer_status); cfg.service(get_relayer_balance); cfg.service(sign); cfg.service(sign_typed_data); cfg.service(sign_transaction); cfg.service(rpc); cfg.service(get_relayer); cfg.service(create_relayer); cfg.service(update_relayer); cfg.service(delete_relayer); cfg.service(list_relayers); }
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use crate::{
222 config::{EvmNetworkConfig, NetworkConfigCommon},
223 jobs::MockJobProducerTrait,
224 models::{
225 AppState, EvmTransactionData, LocalSignerConfigStorage, NetworkConfigData,
226 NetworkRepoModel, NetworkTransactionData, NetworkType, RelayerEvmPolicy,
227 RelayerNetworkPolicy, RelayerRepoModel, SignerConfigStorage, SignerRepoModel,
228 TransactionRepoModel, TransactionStatus, U256,
229 },
230 repositories::{
231 NetworkRepositoryStorage, NotificationRepositoryStorage, PluginRepositoryStorage,
232 RelayerRepositoryStorage, Repository, SignerRepositoryStorage,
233 TransactionCounterRepositoryStorage, TransactionRepositoryStorage,
234 },
235 };
236 use actix_web::{http::StatusCode, test, App};
237 use std::sync::Arc;
238
239 async fn get_test_app_state() -> AppState<
241 MockJobProducerTrait,
242 RelayerRepositoryStorage,
243 TransactionRepositoryStorage,
244 NetworkRepositoryStorage,
245 NotificationRepositoryStorage,
246 SignerRepositoryStorage,
247 TransactionCounterRepositoryStorage,
248 PluginRepositoryStorage,
249 > {
250 let relayer_repo = Arc::new(RelayerRepositoryStorage::new_in_memory());
251 let transaction_repo = Arc::new(TransactionRepositoryStorage::new_in_memory());
252 let signer_repo = Arc::new(SignerRepositoryStorage::new_in_memory());
253 let network_repo = Arc::new(NetworkRepositoryStorage::new_in_memory());
254
255 let test_network = NetworkRepoModel {
259 id: "evm:ethereum".to_string(),
260 name: "ethereum".to_string(),
261 network_type: NetworkType::Evm,
262 config: NetworkConfigData::Evm(EvmNetworkConfig {
263 common: NetworkConfigCommon {
264 network: "ethereum".to_string(),
265 from: None,
266 rpc_urls: Some(vec!["https://rpc.example.com".to_string()]),
267 explorer_urls: None,
268 average_blocktime_ms: Some(12000),
269 is_testnet: Some(false),
270 tags: None,
271 },
272 chain_id: Some(1),
273 required_confirmations: Some(12),
274 features: None,
275 symbol: Some("ETH".to_string()),
276 }),
277 };
278 network_repo.create(test_network).await.unwrap();
279
280 let test_signer = SignerRepoModel {
282 id: "test-signer".to_string(),
283 config: SignerConfigStorage::Local(LocalSignerConfigStorage {
284 raw_key: secrets::SecretVec::new(32, |v| v.copy_from_slice(&[0u8; 32])),
285 }),
286 };
287 signer_repo.create(test_signer).await.unwrap();
288
289 let test_relayer = RelayerRepoModel {
291 id: "test-id".to_string(),
292 name: "Test Relayer".to_string(),
293 network: "ethereum".to_string(),
294 network_type: NetworkType::Evm,
295 signer_id: "test-signer".to_string(),
296 address: "0x1234567890123456789012345678901234567890".to_string(),
297 paused: false,
298 system_disabled: false,
299 policies: RelayerNetworkPolicy::Evm(RelayerEvmPolicy::default()),
300 notification_id: None,
301 custom_rpc_urls: None,
302 };
303 relayer_repo.create(test_relayer).await.unwrap();
304
305 let test_transaction = TransactionRepoModel {
307 id: "tx-123".to_string(),
308 relayer_id: "test-id".to_string(),
309 status: TransactionStatus::Pending,
310 status_reason: None,
311 created_at: chrono::Utc::now().to_rfc3339(),
312 sent_at: None,
313 confirmed_at: None,
314 valid_until: None,
315 network_data: NetworkTransactionData::Evm(EvmTransactionData {
316 gas_price: Some(20000000000u128),
317 gas_limit: Some(21000u64),
318 nonce: Some(1u64),
319 value: U256::from(0u64),
320 data: Some("0x".to_string()),
321 from: "0x1234567890123456789012345678901234567890".to_string(),
322 to: Some("0x9876543210987654321098765432109876543210".to_string()),
323 chain_id: 1u64,
324 hash: Some("0xabcdef".to_string()),
325 signature: None,
326 speed: None,
327 max_fee_per_gas: None,
328 max_priority_fee_per_gas: None,
329 raw: None,
330 }),
331 priced_at: None,
332 hashes: vec!["0xabcdef".to_string()],
333 network_type: NetworkType::Evm,
334 noop_count: None,
335 is_canceled: Some(false),
336 delete_at: None,
337 };
338 transaction_repo.create(test_transaction).await.unwrap();
339
340 AppState {
341 relayer_repository: relayer_repo,
342 transaction_repository: transaction_repo,
343 signer_repository: signer_repo,
344 notification_repository: Arc::new(NotificationRepositoryStorage::new_in_memory()),
345 network_repository: network_repo,
346 transaction_counter_store: Arc::new(
347 TransactionCounterRepositoryStorage::new_in_memory(),
348 ),
349 job_producer: Arc::new(MockJobProducerTrait::new()),
350 plugin_repository: Arc::new(PluginRepositoryStorage::new_in_memory()),
351 }
352 }
353
354 #[actix_web::test]
355 async fn test_routes_are_registered() -> Result<(), color_eyre::eyre::Error> {
356 let app = test::init_service(
358 App::new()
359 .app_data(web::Data::new(get_test_app_state().await))
360 .configure(init),
361 )
362 .await;
363
364 let req = test::TestRequest::get().uri("/relayers").to_request();
368 let resp = test::call_service(&app, req).await;
369 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
370
371 let req = test::TestRequest::get()
373 .uri("/relayers/test-id")
374 .to_request();
375 let resp = test::call_service(&app, req).await;
376 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
377
378 let req = test::TestRequest::patch()
380 .uri("/relayers/test-id")
381 .set_json(serde_json::json!({"paused": false}))
382 .to_request();
383 let resp = test::call_service(&app, req).await;
384 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
385
386 let req = test::TestRequest::get()
388 .uri("/relayers/test-id/status")
389 .to_request();
390 let resp = test::call_service(&app, req).await;
391 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
392
393 let req = test::TestRequest::get()
395 .uri("/relayers/test-id/balance")
396 .to_request();
397 let resp = test::call_service(&app, req).await;
398 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
399
400 let req = test::TestRequest::post()
402 .uri("/relayers/test-id/transactions")
403 .set_json(serde_json::json!({}))
404 .to_request();
405 let resp = test::call_service(&app, req).await;
406 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
407
408 let req = test::TestRequest::get()
410 .uri("/relayers/test-id/transactions/tx-123")
411 .to_request();
412 let resp = test::call_service(&app, req).await;
413 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
414
415 let req = test::TestRequest::get()
417 .uri("/relayers/test-id/transactions/by-nonce/123")
418 .to_request();
419 let resp = test::call_service(&app, req).await;
420 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
421
422 let req = test::TestRequest::get()
424 .uri("/relayers/test-id/transactions")
425 .to_request();
426 let resp = test::call_service(&app, req).await;
427 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
428
429 let req = test::TestRequest::delete()
431 .uri("/relayers/test-id/transactions/pending")
432 .to_request();
433 let resp = test::call_service(&app, req).await;
434 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
435
436 let req = test::TestRequest::delete()
438 .uri("/relayers/test-id/transactions/tx-123")
439 .to_request();
440 let resp = test::call_service(&app, req).await;
441 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
442
443 let req = test::TestRequest::put()
445 .uri("/relayers/test-id/transactions/tx-123")
446 .set_json(serde_json::json!({}))
447 .to_request();
448 let resp = test::call_service(&app, req).await;
449 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
450
451 let req = test::TestRequest::post()
453 .uri("/relayers/test-id/sign")
454 .set_json(serde_json::json!({
455 "message": "0x1234567890abcdef"
456 }))
457 .to_request();
458 let resp = test::call_service(&app, req).await;
459 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
460
461 let req = test::TestRequest::post()
463 .uri("/relayers/test-id/sign-typed-data")
464 .set_json(serde_json::json!({
465 "domain_separator": "0x1234567890abcdef",
466 "hash_struct_message": "0x1234567890abcdef"
467 }))
468 .to_request();
469 let resp = test::call_service(&app, req).await;
470 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
471
472 let req = test::TestRequest::post()
474 .uri("/relayers/test-id/rpc")
475 .set_json(serde_json::json!({
476 "jsonrpc": "2.0",
477 "method": "eth_getBlockByNumber",
478 "params": ["0x1", true],
479 "id": 1
480 }))
481 .to_request();
482 let resp = test::call_service(&app, req).await;
483 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
484
485 Ok(())
486 }
487}