1use actix_web::web::ThinData;
12use serde::{Deserialize, Serialize};
13use std::sync::Arc;
14use tracing::instrument;
15use utoipa::ToSchema;
16
17#[cfg(test)]
18use mockall::automock;
19
20use crate::{
21 jobs::JobProducerTrait,
22 models::{
23 transaction::request::{
24 SponsoredTransactionBuildRequest, SponsoredTransactionQuoteRequest,
25 },
26 AppState, DecoratedSignature, DeletePendingTransactionsResponse,
27 EncodedSerializedTransaction, EvmNetwork, EvmTransactionDataSignature, JsonRpcRequest,
28 JsonRpcResponse, NetworkRepoModel, NetworkRpcRequest, NetworkRpcResult,
29 NetworkTransactionRequest, NetworkType, NotificationRepoModel, RelayerError,
30 RelayerRepoModel, RelayerStatus, SignerRepoModel, SponsoredTransactionBuildResponse,
31 SponsoredTransactionQuoteResponse, TransactionError, TransactionRepoModel,
32 },
33 repositories::{
34 ApiKeyRepositoryTrait, NetworkRepository, PluginRepositoryTrait, RelayerRepository,
35 Repository, TransactionCounterTrait, TransactionRepository,
36 },
37 services::{
38 provider::get_network_provider, signer::EvmSignerFactory, TransactionCounterService,
39 },
40};
41
42use async_trait::async_trait;
43use eyre::Result;
44
45mod evm;
46mod solana;
47mod stellar;
48mod util;
49
50pub use evm::*;
51pub use solana::*;
52pub use stellar::*;
53pub use util::*;
54
55pub use solana::SwapResult;
57
58#[async_trait]
62#[cfg_attr(test, automock)]
63#[allow(dead_code)]
64pub trait Relayer {
65 async fn process_transaction_request(
76 &self,
77 tx_request: NetworkTransactionRequest,
78 ) -> Result<TransactionRepoModel, RelayerError>;
79
80 async fn get_balance(&self) -> Result<BalanceResponse, RelayerError>;
87
88 async fn delete_pending_transactions(
95 &self,
96 ) -> Result<DeletePendingTransactionsResponse, RelayerError>;
97
98 async fn sign_data(&self, request: SignDataRequest) -> Result<SignDataResponse, RelayerError>;
109
110 async fn sign_typed_data(
121 &self,
122 request: SignTypedDataRequest,
123 ) -> Result<SignDataResponse, RelayerError>;
124
125 async fn rpc(
136 &self,
137 request: JsonRpcRequest<NetworkRpcRequest>,
138 ) -> Result<JsonRpcResponse<NetworkRpcResult>, RelayerError>;
139
140 async fn get_status(&self) -> Result<RelayerStatus, RelayerError>;
147
148 async fn initialize_relayer(&self) -> Result<(), RelayerError>;
154
155 async fn check_health(&self) -> Result<(), Vec<crate::models::HealthCheckFailure>>;
165
166 async fn validate_min_balance(&self) -> Result<(), RelayerError>;
172
173 async fn sign_transaction(
184 &self,
185 request: &SignTransactionRequest,
186 ) -> Result<SignTransactionExternalResponse, RelayerError>;
187}
188
189#[async_trait]
192#[allow(dead_code)]
193#[cfg_attr(test, automock)]
194pub trait SolanaRelayerDexTrait {
195 async fn handle_token_swap_request(
197 &self,
198 relayer_id: String,
199 ) -> Result<Vec<SwapResult>, RelayerError>;
200}
201
202#[async_trait]
204#[allow(dead_code)]
205#[cfg_attr(test, automock)]
206pub trait StellarRelayerDexTrait {
207 async fn handle_token_swap_request(
209 &self,
210 relayer_id: String,
211 ) -> Result<Vec<SwapResult>, RelayerError>;
212}
213
214#[async_trait]
219#[allow(dead_code)]
220#[cfg_attr(test, automock)]
221pub trait GasAbstractionTrait {
222 async fn quote_sponsored_transaction(
232 &self,
233 params: SponsoredTransactionQuoteRequest,
234 ) -> Result<SponsoredTransactionQuoteResponse, RelayerError>;
235
236 async fn build_sponsored_transaction(
246 &self,
247 params: SponsoredTransactionBuildRequest,
248 ) -> Result<SponsoredTransactionBuildResponse, RelayerError>;
249}
250
251pub enum NetworkRelayer<
252 J: JobProducerTrait + 'static,
253 T: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
254 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
255 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
256 TCR: TransactionCounterTrait + Send + Sync + 'static,
257> {
258 Evm(Box<DefaultEvmRelayer<J, T, RR, NR, TCR>>),
259 Solana(DefaultSolanaRelayer<J, T, RR, NR>),
260 Stellar(DefaultStellarRelayer<J, T, NR, RR, TCR>),
261}
262
263#[async_trait]
264impl<
265 J: JobProducerTrait + 'static,
266 T: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
267 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
268 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
269 TCR: TransactionCounterTrait + Send + Sync + 'static,
270 > Relayer for NetworkRelayer<J, T, RR, NR, TCR>
271{
272 async fn process_transaction_request(
273 &self,
274 tx_request: NetworkTransactionRequest,
275 ) -> Result<TransactionRepoModel, RelayerError> {
276 match self {
277 NetworkRelayer::Evm(relayer) => relayer.process_transaction_request(tx_request).await,
278 NetworkRelayer::Solana(relayer) => {
279 relayer.process_transaction_request(tx_request).await
280 }
281 NetworkRelayer::Stellar(relayer) => {
282 relayer.process_transaction_request(tx_request).await
283 }
284 }
285 }
286
287 async fn get_balance(&self) -> Result<BalanceResponse, RelayerError> {
288 match self {
289 NetworkRelayer::Evm(relayer) => relayer.get_balance().await,
290 NetworkRelayer::Solana(relayer) => relayer.get_balance().await,
291 NetworkRelayer::Stellar(relayer) => relayer.get_balance().await,
292 }
293 }
294
295 async fn delete_pending_transactions(
296 &self,
297 ) -> Result<DeletePendingTransactionsResponse, RelayerError> {
298 match self {
299 NetworkRelayer::Evm(relayer) => relayer.delete_pending_transactions().await,
300 NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
301 NetworkRelayer::Stellar(relayer) => relayer.delete_pending_transactions().await,
302 }
303 }
304
305 async fn sign_data(&self, request: SignDataRequest) -> Result<SignDataResponse, RelayerError> {
306 match self {
307 NetworkRelayer::Evm(relayer) => relayer.sign_data(request).await,
308 NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
309 NetworkRelayer::Stellar(relayer) => relayer.sign_data(request).await,
310 }
311 }
312
313 async fn sign_typed_data(
314 &self,
315 request: SignTypedDataRequest,
316 ) -> Result<SignDataResponse, RelayerError> {
317 match self {
318 NetworkRelayer::Evm(relayer) => relayer.sign_typed_data(request).await,
319 NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
320 NetworkRelayer::Stellar(relayer) => relayer.sign_typed_data(request).await,
321 }
322 }
323
324 async fn rpc(
325 &self,
326 request: JsonRpcRequest<NetworkRpcRequest>,
327 ) -> Result<JsonRpcResponse<NetworkRpcResult>, RelayerError> {
328 match self {
329 NetworkRelayer::Evm(relayer) => relayer.rpc(request).await,
330 NetworkRelayer::Solana(relayer) => relayer.rpc(request).await,
331 NetworkRelayer::Stellar(relayer) => relayer.rpc(request).await,
332 }
333 }
334
335 async fn get_status(&self) -> Result<RelayerStatus, RelayerError> {
336 match self {
337 NetworkRelayer::Evm(relayer) => relayer.get_status().await,
338 NetworkRelayer::Solana(relayer) => relayer.get_status().await,
339 NetworkRelayer::Stellar(relayer) => relayer.get_status().await,
340 }
341 }
342
343 async fn validate_min_balance(&self) -> Result<(), RelayerError> {
344 match self {
345 NetworkRelayer::Evm(relayer) => relayer.validate_min_balance().await,
346 NetworkRelayer::Solana(relayer) => relayer.validate_min_balance().await,
347 NetworkRelayer::Stellar(relayer) => relayer.validate_min_balance().await,
348 }
349 }
350
351 async fn initialize_relayer(&self) -> Result<(), RelayerError> {
352 match self {
353 NetworkRelayer::Evm(relayer) => relayer.initialize_relayer().await,
354 NetworkRelayer::Solana(relayer) => relayer.initialize_relayer().await,
355 NetworkRelayer::Stellar(relayer) => relayer.initialize_relayer().await,
356 }
357 }
358
359 async fn check_health(&self) -> Result<(), Vec<crate::models::HealthCheckFailure>> {
360 match self {
361 NetworkRelayer::Evm(relayer) => relayer.check_health().await,
362 NetworkRelayer::Solana(relayer) => relayer.check_health().await,
363 NetworkRelayer::Stellar(relayer) => relayer.check_health().await,
364 }
365 }
366
367 async fn sign_transaction(
368 &self,
369 request: &SignTransactionRequest,
370 ) -> Result<SignTransactionExternalResponse, RelayerError> {
371 match self {
372 NetworkRelayer::Evm(_) => Err(RelayerError::NotSupported(
373 "sign_transaction not supported for EVM".to_string(),
374 )),
375 NetworkRelayer::Solana(relayer) => relayer.sign_transaction(request).await,
376 NetworkRelayer::Stellar(relayer) => relayer.sign_transaction(request).await,
377 }
378 }
379}
380
381#[async_trait]
382impl<
383 J: JobProducerTrait + 'static,
384 T: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
385 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
386 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
387 TCR: TransactionCounterTrait + Send + Sync + 'static,
388 > GasAbstractionTrait for NetworkRelayer<J, T, RR, NR, TCR>
389{
390 async fn quote_sponsored_transaction(
391 &self,
392 params: SponsoredTransactionQuoteRequest,
393 ) -> Result<SponsoredTransactionQuoteResponse, RelayerError> {
394 match params {
395 SponsoredTransactionQuoteRequest::Solana(params) => match self {
396 NetworkRelayer::Solana(relayer) => {
397 relayer
398 .quote_sponsored_transaction(SponsoredTransactionQuoteRequest::Solana(
399 params,
400 ))
401 .await
402 }
403 NetworkRelayer::Stellar(_) => Err(RelayerError::ValidationError(
404 "Solana request type does not match Stellar relayer type".to_string(),
405 )),
406 NetworkRelayer::Evm(_) => Err(RelayerError::NotSupported(
407 "Gas abstraction not supported for EVM relayers".to_string(),
408 )),
409 },
410 SponsoredTransactionQuoteRequest::Stellar(params) => match self {
411 NetworkRelayer::Stellar(relayer) => {
412 relayer
413 .quote_sponsored_transaction(SponsoredTransactionQuoteRequest::Stellar(
414 params,
415 ))
416 .await
417 }
418 NetworkRelayer::Solana(_) => Err(RelayerError::ValidationError(
419 "Stellar request type does not match Solana relayer type".to_string(),
420 )),
421 NetworkRelayer::Evm(_) => Err(RelayerError::NotSupported(
422 "Gas abstraction not supported for EVM relayers".to_string(),
423 )),
424 },
425 }
426 }
427
428 async fn build_sponsored_transaction(
429 &self,
430 params: SponsoredTransactionBuildRequest,
431 ) -> Result<SponsoredTransactionBuildResponse, RelayerError> {
432 match params {
433 SponsoredTransactionBuildRequest::Solana(params) => match self {
434 NetworkRelayer::Solana(relayer) => {
435 relayer
436 .build_sponsored_transaction(SponsoredTransactionBuildRequest::Solana(
437 params,
438 ))
439 .await
440 }
441 NetworkRelayer::Stellar(_) => Err(RelayerError::ValidationError(
442 "Solana request type does not match Stellar relayer type".to_string(),
443 )),
444 NetworkRelayer::Evm(_) => Err(RelayerError::NotSupported(
445 "Gas abstraction not supported for EVM relayers".to_string(),
446 )),
447 },
448 SponsoredTransactionBuildRequest::Stellar(params) => match self {
449 NetworkRelayer::Stellar(relayer) => {
450 relayer
451 .build_sponsored_transaction(SponsoredTransactionBuildRequest::Stellar(
452 params,
453 ))
454 .await
455 }
456 NetworkRelayer::Solana(_) => Err(RelayerError::ValidationError(
457 "Stellar request type does not match Solana relayer type".to_string(),
458 )),
459 NetworkRelayer::Evm(_) => Err(RelayerError::NotSupported(
460 "Gas abstraction not supported for EVM relayers".to_string(),
461 )),
462 },
463 }
464 }
465}
466
467impl<
468 J: JobProducerTrait + 'static,
469 T: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
470 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
471 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
472 TCR: TransactionCounterTrait + Send + Sync + 'static,
473 > NetworkRelayer<J, T, RR, NR, TCR>
474{
475 pub async fn handle_token_swap_request(
482 &self,
483 relayer_id: String,
484 ) -> Result<Vec<SwapResult>, RelayerError> {
485 match self {
486 NetworkRelayer::Evm(_) => Err(RelayerError::NotSupported(
487 "Token swap not supported for EVM relayers".to_string(),
488 )),
489 NetworkRelayer::Solana(relayer) => relayer.handle_token_swap_request(relayer_id).await,
490 NetworkRelayer::Stellar(relayer) => relayer.handle_token_swap_request(relayer_id).await,
491 }
492 }
493}
494
495#[async_trait]
496pub trait RelayerFactoryTrait<
497 J: JobProducerTrait + Send + Sync + 'static,
498 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
499 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
500 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
501 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
502 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
503 TCR: TransactionCounterTrait + Send + Sync + 'static,
504 PR: PluginRepositoryTrait + Send + Sync + 'static,
505 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
506>
507{
508 async fn create_relayer(
509 relayer: RelayerRepoModel,
510 signer: SignerRepoModel,
511 state: &ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
512 ) -> Result<NetworkRelayer<J, TR, RR, NR, TCR>, RelayerError>;
513}
514
515pub struct RelayerFactory;
516
517#[async_trait]
518impl<
519 J: JobProducerTrait + 'static,
520 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
521 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
522 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
523 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
524 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
525 TCR: TransactionCounterTrait + Send + Sync + 'static,
526 PR: PluginRepositoryTrait + Send + Sync + 'static,
527 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
528 > RelayerFactoryTrait<J, RR, TR, NR, NFR, SR, TCR, PR, AKR> for RelayerFactory
529{
530 #[instrument(
531 level = "debug",
532 skip(relayer, signer, state),
533 fields(
534 request_id = ?crate::observability::request_id::get_request_id(),
535 relayer_id = %relayer.id,
536 network_type = ?relayer.network_type,
537 )
538 )]
539 async fn create_relayer(
540 relayer: RelayerRepoModel,
541 signer: SignerRepoModel,
542 state: &ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
543 ) -> Result<NetworkRelayer<J, TR, RR, NR, TCR>, RelayerError> {
544 match relayer.network_type {
545 NetworkType::Evm => {
546 let network_repo = state
547 .network_repository()
548 .get_by_name(NetworkType::Evm, &relayer.network)
549 .await
550 .ok()
551 .flatten()
552 .ok_or_else(|| {
553 RelayerError::NetworkConfiguration(format!(
554 "Network {} not found",
555 relayer.network
556 ))
557 })?;
558
559 let network = EvmNetwork::try_from(network_repo)?;
560
561 let evm_provider = get_network_provider(&network, relayer.custom_rpc_urls.clone())?;
562 let signer_service = EvmSignerFactory::create_evm_signer(signer.into()).await?;
563 let transaction_counter_service = Arc::new(TransactionCounterService::new(
564 relayer.id.clone(),
565 relayer.address.clone(),
566 state.transaction_counter_store(),
567 ));
568 let relayer = DefaultEvmRelayer::new(
569 relayer,
570 signer_service,
571 evm_provider,
572 network,
573 state.relayer_repository(),
574 state.network_repository(),
575 state.transaction_repository(),
576 transaction_counter_service,
577 state.job_producer(),
578 )?;
579
580 Ok(NetworkRelayer::Evm(Box::new(relayer)))
581 }
582 NetworkType::Solana => {
583 let solana_relayer = create_solana_relayer(
584 relayer,
585 signer,
586 state.relayer_repository(),
587 state.network_repository(),
588 state.transaction_repository(),
589 state.job_producer(),
590 )
591 .await?;
592 Ok(NetworkRelayer::Solana(solana_relayer))
593 }
594 NetworkType::Stellar => {
595 let stellar_relayer = create_stellar_relayer(
596 relayer,
597 signer,
598 state.relayer_repository(),
599 state.network_repository(),
600 state.transaction_repository(),
601 state.job_producer(),
602 state.transaction_counter_store(),
603 )
604 .await?;
605 Ok(NetworkRelayer::Stellar(stellar_relayer))
606 }
607 }
608 }
609}
610
611#[derive(Serialize, Deserialize, ToSchema)]
612pub struct SignDataRequest {
613 pub message: String,
614}
615
616#[derive(Serialize, Deserialize, ToSchema)]
617pub struct SignDataResponseEvm {
618 pub r: String,
619 pub s: String,
620 pub v: u8,
621 pub sig: String,
622}
623
624#[derive(Serialize, Deserialize, ToSchema)]
625pub struct SignDataResponseSolana {
626 pub signature: String,
627 pub public_key: String,
628}
629
630#[derive(Serialize, Deserialize, ToSchema)]
631#[serde(untagged)]
632pub enum SignDataResponse {
633 Evm(SignDataResponseEvm),
634 Solana(SignDataResponseSolana),
635}
636
637#[derive(Serialize, Deserialize, ToSchema)]
638pub struct SignTypedDataRequest {
639 pub domain_separator: String,
640 pub hash_struct_message: String,
641}
642
643#[derive(Debug, Serialize, Deserialize, ToSchema)]
644pub struct SignTransactionRequestStellar {
645 pub unsigned_xdr: String,
646}
647
648#[derive(Debug, Serialize, Deserialize, ToSchema)]
649pub struct SignTransactionRequestSolana {
650 pub transaction: EncodedSerializedTransaction,
651}
652
653#[derive(Debug, Serialize, Deserialize, ToSchema)]
654#[serde(untagged)]
655pub enum SignTransactionRequest {
656 Stellar(SignTransactionRequestStellar),
657 Evm(Vec<u8>),
658 Solana(SignTransactionRequestSolana),
659}
660
661#[derive(Debug, Serialize, Deserialize, Clone)]
662pub struct SignTransactionResponseEvm {
663 pub hash: String,
664 pub signature: EvmTransactionDataSignature,
665 pub raw: Vec<u8>,
666}
667
668#[derive(Debug, Serialize, Deserialize, Clone)]
669pub struct SignTransactionResponseStellar {
670 pub signature: DecoratedSignature,
671}
672
673#[derive(Debug, Serialize, Deserialize, ToSchema, Clone)]
674pub struct SignTransactionResponseSolana {
675 pub transaction: EncodedSerializedTransaction,
676 pub signature: String,
677}
678
679#[derive(Debug, Serialize, Deserialize)]
680#[serde(rename_all = "camelCase")]
681pub struct SignXdrTransactionResponseStellar {
682 pub signed_xdr: String,
683 pub signature: DecoratedSignature,
684}
685
686#[derive(Debug, Serialize, Deserialize, Clone)]
687pub enum SignTransactionResponse {
688 Evm(SignTransactionResponseEvm),
689 Solana(SignTransactionResponseSolana),
690 Stellar(SignTransactionResponseStellar),
691}
692
693#[derive(Debug, Serialize, Deserialize, ToSchema)]
694#[serde(rename_all = "camelCase")]
695#[schema(as = SignTransactionResponseStellar)]
696pub struct SignTransactionExternalResponseStellar {
697 pub signed_xdr: String,
698 pub signature: String,
699}
700
701#[derive(Debug, Serialize, Deserialize, ToSchema)]
702#[serde(untagged)]
703#[schema(as = SignTransactionResponse)]
704pub enum SignTransactionExternalResponse {
705 Stellar(SignTransactionExternalResponseStellar),
706 Evm(Vec<u8>),
707 Solana(SignTransactionResponseSolana),
708}
709
710impl SignTransactionResponse {
711 pub fn into_evm(self) -> Result<SignTransactionResponseEvm, TransactionError> {
712 match self {
713 SignTransactionResponse::Evm(e) => Ok(e),
714 _ => Err(TransactionError::InvalidType(
715 "Expected EVM signature".to_string(),
716 )),
717 }
718 }
719}
720
721#[derive(Debug, Serialize, ToSchema)]
722pub struct BalanceResponse {
723 pub balance: u128,
724 #[schema(example = "wei")]
725 pub unit: String,
726}