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