1use actix_web::web::ThinData;
12use serde::{Deserialize, Serialize};
13use std::sync::Arc;
14use utoipa::ToSchema;
15
16#[cfg(test)]
17use mockall::automock;
18
19use crate::{
20 jobs::JobProducerTrait,
21 models::{
22 AppState, DecoratedSignature, DeletePendingTransactionsResponse, EvmNetwork,
23 EvmTransactionDataSignature, JsonRpcRequest, JsonRpcResponse, NetworkRepoModel,
24 NetworkRpcRequest, NetworkRpcResult, NetworkTransactionRequest, NetworkType,
25 NotificationRepoModel, RelayerError, RelayerRepoModel, RelayerStatus, SignerRepoModel,
26 StellarNetwork, TransactionError, TransactionRepoModel,
27 },
28 repositories::{
29 ApiKeyRepositoryTrait, NetworkRepository, PluginRepositoryTrait, RelayerRepository,
30 Repository, TransactionCounterTrait, TransactionRepository,
31 },
32 services::{
33 get_network_provider, EvmSignerFactory, StellarSignerFactory, TransactionCounterService,
34 },
35};
36
37use async_trait::async_trait;
38use eyre::Result;
39
40mod evm;
41mod solana;
42mod stellar;
43mod util;
44
45pub use evm::*;
46pub use solana::*;
47pub use stellar::*;
48pub use util::*;
49
50#[async_trait]
54#[cfg_attr(test, automock)]
55#[allow(dead_code)]
56pub trait Relayer {
57 async fn process_transaction_request(
68 &self,
69 tx_request: NetworkTransactionRequest,
70 ) -> Result<TransactionRepoModel, RelayerError>;
71
72 async fn get_balance(&self) -> Result<BalanceResponse, RelayerError>;
79
80 async fn delete_pending_transactions(
87 &self,
88 ) -> Result<DeletePendingTransactionsResponse, RelayerError>;
89
90 async fn sign_data(&self, request: SignDataRequest) -> Result<SignDataResponse, RelayerError>;
101
102 async fn sign_typed_data(
113 &self,
114 request: SignTypedDataRequest,
115 ) -> Result<SignDataResponse, RelayerError>;
116
117 async fn rpc(
128 &self,
129 request: JsonRpcRequest<NetworkRpcRequest>,
130 ) -> Result<JsonRpcResponse<NetworkRpcResult>, RelayerError>;
131
132 async fn get_status(&self) -> Result<RelayerStatus, RelayerError>;
139
140 async fn initialize_relayer(&self) -> Result<(), RelayerError>;
146
147 async fn check_health(&self) -> Result<(), Vec<crate::models::HealthCheckFailure>>;
157
158 async fn validate_min_balance(&self) -> Result<(), RelayerError>;
164
165 async fn sign_transaction(
176 &self,
177 request: &SignTransactionRequest,
178 ) -> Result<SignTransactionExternalResponse, RelayerError>;
179}
180
181#[async_trait]
184#[allow(dead_code)]
185#[cfg_attr(test, automock)]
186pub trait SolanaRelayerDexTrait {
187 async fn handle_token_swap_request(
189 &self,
190 relayer_id: String,
191 ) -> Result<Vec<SwapResult>, RelayerError>;
192}
193
194#[async_trait]
197#[allow(dead_code)]
198#[cfg_attr(test, automock)]
199pub trait SolanaRelayerTrait {
200 async fn get_balance(&self) -> Result<BalanceResponse, RelayerError>;
207
208 async fn rpc(
219 &self,
220 request: JsonRpcRequest<NetworkRpcRequest>,
221 ) -> Result<JsonRpcResponse<NetworkRpcResult>, RelayerError>;
222
223 async fn check_health(&self) -> Result<(), Vec<crate::models::HealthCheckFailure>>;
230
231 async fn initialize_relayer(&self) -> Result<(), RelayerError>;
237
238 async fn validate_min_balance(&self) -> Result<(), RelayerError>;
244}
245
246pub enum NetworkRelayer<
247 J: JobProducerTrait + 'static,
248 T: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
249 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
250 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
251 TCR: TransactionCounterTrait + Send + Sync + 'static,
252> {
253 Evm(DefaultEvmRelayer<J, T, RR, NR, TCR>),
254 Solana(DefaultSolanaRelayer<J, T, RR, NR>),
255 Stellar(DefaultStellarRelayer<J, T, NR, RR, TCR>),
256}
257
258#[async_trait]
259impl<
260 J: JobProducerTrait + 'static,
261 T: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
262 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
263 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
264 TCR: TransactionCounterTrait + Send + Sync + 'static,
265 > Relayer for NetworkRelayer<J, T, RR, NR, TCR>
266{
267 async fn process_transaction_request(
268 &self,
269 tx_request: NetworkTransactionRequest,
270 ) -> Result<TransactionRepoModel, RelayerError> {
271 match self {
272 NetworkRelayer::Evm(relayer) => relayer.process_transaction_request(tx_request).await,
273 NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
274 NetworkRelayer::Stellar(relayer) => {
275 relayer.process_transaction_request(tx_request).await
276 }
277 }
278 }
279
280 async fn get_balance(&self) -> Result<BalanceResponse, RelayerError> {
281 match self {
282 NetworkRelayer::Evm(relayer) => relayer.get_balance().await,
283 NetworkRelayer::Solana(relayer) => relayer.get_balance().await,
284 NetworkRelayer::Stellar(relayer) => relayer.get_balance().await,
285 }
286 }
287
288 async fn delete_pending_transactions(
289 &self,
290 ) -> Result<DeletePendingTransactionsResponse, RelayerError> {
291 match self {
292 NetworkRelayer::Evm(relayer) => relayer.delete_pending_transactions().await,
293 NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
294 NetworkRelayer::Stellar(relayer) => relayer.delete_pending_transactions().await,
295 }
296 }
297
298 async fn sign_data(&self, request: SignDataRequest) -> Result<SignDataResponse, RelayerError> {
299 match self {
300 NetworkRelayer::Evm(relayer) => relayer.sign_data(request).await,
301 NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
302 NetworkRelayer::Stellar(relayer) => relayer.sign_data(request).await,
303 }
304 }
305
306 async fn sign_typed_data(
307 &self,
308 request: SignTypedDataRequest,
309 ) -> Result<SignDataResponse, RelayerError> {
310 match self {
311 NetworkRelayer::Evm(relayer) => relayer.sign_typed_data(request).await,
312 NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
313 NetworkRelayer::Stellar(relayer) => relayer.sign_typed_data(request).await,
314 }
315 }
316
317 async fn rpc(
318 &self,
319 request: JsonRpcRequest<NetworkRpcRequest>,
320 ) -> Result<JsonRpcResponse<NetworkRpcResult>, RelayerError> {
321 match self {
322 NetworkRelayer::Evm(relayer) => relayer.rpc(request).await,
323 NetworkRelayer::Solana(relayer) => relayer.rpc(request).await,
324 NetworkRelayer::Stellar(relayer) => relayer.rpc(request).await,
325 }
326 }
327
328 async fn get_status(&self) -> Result<RelayerStatus, RelayerError> {
329 match self {
330 NetworkRelayer::Evm(relayer) => relayer.get_status().await,
331 NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
332 NetworkRelayer::Stellar(relayer) => relayer.get_status().await,
333 }
334 }
335
336 async fn validate_min_balance(&self) -> Result<(), RelayerError> {
337 match self {
338 NetworkRelayer::Evm(relayer) => relayer.validate_min_balance().await,
339 NetworkRelayer::Solana(relayer) => relayer.validate_min_balance().await,
340 NetworkRelayer::Stellar(relayer) => relayer.validate_min_balance().await,
341 }
342 }
343
344 async fn initialize_relayer(&self) -> Result<(), RelayerError> {
345 match self {
346 NetworkRelayer::Evm(relayer) => relayer.initialize_relayer().await,
347 NetworkRelayer::Solana(relayer) => relayer.initialize_relayer().await,
348 NetworkRelayer::Stellar(relayer) => relayer.initialize_relayer().await,
349 }
350 }
351
352 async fn check_health(&self) -> Result<(), Vec<crate::models::HealthCheckFailure>> {
353 match self {
354 NetworkRelayer::Evm(relayer) => relayer.check_health().await,
355 NetworkRelayer::Solana(relayer) => relayer.check_health().await,
356 NetworkRelayer::Stellar(relayer) => relayer.check_health().await,
357 }
358 }
359
360 async fn sign_transaction(
361 &self,
362 request: &SignTransactionRequest,
363 ) -> Result<SignTransactionExternalResponse, RelayerError> {
364 match self {
365 NetworkRelayer::Evm(_) => Err(RelayerError::NotSupported(
366 "sign_transaction not supported for EVM".to_string(),
367 )),
368 NetworkRelayer::Solana(_) => Err(RelayerError::NotSupported(
369 "sign_transaction not supported for Solana".to_string(),
370 )),
371 NetworkRelayer::Stellar(relayer) => relayer.sign_transaction(request).await,
372 }
373 }
374}
375
376#[async_trait]
377pub trait RelayerFactoryTrait<
378 J: JobProducerTrait + Send + Sync + 'static,
379 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
380 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
381 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
382 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
383 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
384 TCR: TransactionCounterTrait + Send + Sync + 'static,
385 PR: PluginRepositoryTrait + Send + Sync + 'static,
386 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
387>
388{
389 async fn create_relayer(
390 relayer: RelayerRepoModel,
391 signer: SignerRepoModel,
392 state: &ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
393 ) -> Result<NetworkRelayer<J, TR, RR, NR, TCR>, RelayerError>;
394}
395
396pub struct RelayerFactory;
397
398#[async_trait]
399impl<
400 J: JobProducerTrait + 'static,
401 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
402 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
403 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
404 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
405 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
406 TCR: TransactionCounterTrait + Send + Sync + 'static,
407 PR: PluginRepositoryTrait + Send + Sync + 'static,
408 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
409 > RelayerFactoryTrait<J, RR, TR, NR, NFR, SR, TCR, PR, AKR> for RelayerFactory
410{
411 async fn create_relayer(
412 relayer: RelayerRepoModel,
413 signer: SignerRepoModel,
414 state: &ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
415 ) -> Result<NetworkRelayer<J, TR, RR, NR, TCR>, RelayerError> {
416 match relayer.network_type {
417 NetworkType::Evm => {
418 let network_repo = state
419 .network_repository()
420 .get_by_name(NetworkType::Evm, &relayer.network)
421 .await
422 .ok()
423 .flatten()
424 .ok_or_else(|| {
425 RelayerError::NetworkConfiguration(format!(
426 "Network {} not found",
427 relayer.network
428 ))
429 })?;
430
431 let network = EvmNetwork::try_from(network_repo)?;
432
433 let evm_provider = get_network_provider(&network, relayer.custom_rpc_urls.clone())?;
434 let signer_service = EvmSignerFactory::create_evm_signer(signer.into()).await?;
435 let transaction_counter_service = Arc::new(TransactionCounterService::new(
436 relayer.id.clone(),
437 relayer.address.clone(),
438 state.transaction_counter_store(),
439 ));
440 let relayer = DefaultEvmRelayer::new(
441 relayer,
442 signer_service,
443 evm_provider,
444 network,
445 state.relayer_repository(),
446 state.network_repository(),
447 state.transaction_repository(),
448 transaction_counter_service,
449 state.job_producer(),
450 )?;
451
452 Ok(NetworkRelayer::Evm(relayer))
453 }
454 NetworkType::Solana => {
455 let solana_relayer = create_solana_relayer(
456 relayer,
457 signer,
458 state.relayer_repository(),
459 state.network_repository(),
460 state.transaction_repository(),
461 state.job_producer(),
462 )
463 .await?;
464 Ok(NetworkRelayer::Solana(solana_relayer))
465 }
466 NetworkType::Stellar => {
467 let network_repo = state
468 .network_repository()
469 .get_by_name(NetworkType::Stellar, &relayer.network)
470 .await
471 .ok()
472 .flatten()
473 .ok_or_else(|| {
474 RelayerError::NetworkConfiguration(format!(
475 "Network {} not found",
476 relayer.network
477 ))
478 })?;
479
480 let network = StellarNetwork::try_from(network_repo)?;
481
482 let stellar_provider =
483 get_network_provider(&network, relayer.custom_rpc_urls.clone())
484 .map_err(|e| RelayerError::NetworkConfiguration(e.to_string()))?;
485
486 let signer_service = StellarSignerFactory::create_stellar_signer(&signer.into())?;
487
488 let transaction_counter_service = Arc::new(TransactionCounterService::new(
489 relayer.id.clone(),
490 relayer.address.clone(),
491 state.transaction_counter_store(),
492 ));
493
494 let relayer = DefaultStellarRelayer::<J, TR, NR, RR, TCR>::new(
495 relayer,
496 signer_service,
497 stellar_provider,
498 stellar::StellarRelayerDependencies::new(
499 state.relayer_repository(),
500 state.network_repository(),
501 state.transaction_repository(),
502 transaction_counter_service,
503 state.job_producer(),
504 ),
505 )
506 .await?;
507 Ok(NetworkRelayer::Stellar(relayer))
508 }
509 }
510 }
511}
512
513#[derive(Serialize, Deserialize, ToSchema)]
514pub struct SignDataRequest {
515 pub message: String,
516}
517
518#[derive(Serialize, Deserialize, ToSchema)]
519pub struct SignDataResponseEvm {
520 pub r: String,
521 pub s: String,
522 pub v: u8,
523 pub sig: String,
524}
525
526#[derive(Serialize, Deserialize, ToSchema)]
527pub struct SignDataResponseSolana {
528 pub signature: String,
529 pub public_key: String,
530}
531
532#[derive(Serialize, Deserialize, ToSchema)]
533#[serde(untagged)]
534pub enum SignDataResponse {
535 Evm(SignDataResponseEvm),
536 Solana(SignDataResponseSolana),
537}
538
539#[derive(Serialize, Deserialize, ToSchema)]
540pub struct SignTypedDataRequest {
541 pub domain_separator: String,
542 pub hash_struct_message: String,
543}
544
545#[derive(Debug, Serialize, Deserialize, ToSchema)]
546pub struct SignTransactionRequestStellar {
547 pub unsigned_xdr: String,
548}
549
550#[derive(Debug, Serialize, Deserialize, ToSchema)]
551#[serde(untagged)]
552pub enum SignTransactionRequest {
553 Stellar(SignTransactionRequestStellar),
554 Evm(Vec<u8>),
555 Solana(Vec<u8>),
556}
557
558#[derive(Debug, Serialize, Deserialize)]
559pub struct SignTransactionResponseEvm {
560 pub hash: String,
561 pub signature: EvmTransactionDataSignature,
562 pub raw: Vec<u8>,
563}
564
565#[derive(Debug, Serialize, Deserialize)]
566pub struct SignTransactionResponseStellar {
567 pub signature: DecoratedSignature,
568}
569
570#[derive(Debug, Serialize, Deserialize)]
571#[serde(rename_all = "camelCase")]
572pub struct SignXdrTransactionResponseStellar {
573 pub signed_xdr: String,
574 pub signature: DecoratedSignature,
575}
576
577#[derive(Debug, Serialize, Deserialize)]
578pub enum SignTransactionResponse {
579 Evm(SignTransactionResponseEvm),
580 Solana(Vec<u8>),
581 Stellar(SignTransactionResponseStellar),
582}
583
584#[derive(Debug, Serialize, Deserialize, ToSchema)]
585#[serde(rename_all = "camelCase")]
586#[schema(as = SignTransactionResponseStellar)]
587pub struct SignTransactionExternalResponseStellar {
588 pub signed_xdr: String,
589 pub signature: String,
590}
591
592#[derive(Debug, Serialize, Deserialize, ToSchema)]
593#[serde(untagged)]
594#[schema(as = SignTransactionResponse)]
595pub enum SignTransactionExternalResponse {
596 Stellar(SignTransactionExternalResponseStellar),
597 Evm(Vec<u8>),
598 Solana(Vec<u8>),
599}
600
601impl SignTransactionResponse {
602 pub fn into_evm(self) -> Result<SignTransactionResponseEvm, TransactionError> {
603 match self {
604 SignTransactionResponse::Evm(e) => Ok(e),
605 _ => Err(TransactionError::InvalidType(
606 "Expected EVM signature".to_string(),
607 )),
608 }
609 }
610}
611
612#[derive(Debug, Serialize, ToSchema)]
613pub struct BalanceResponse {
614 pub balance: u128,
615 #[schema(example = "wei")]
616 pub unit: String,
617}