openzeppelin_relayer/domain/relayer/
mod.rs

1//! # Relayer Domain Module
2//!
3//! This module contains the core domain logic for the relayer service.
4//! It handles transaction submission, validation, and monitoring across
5//! different blockchain networks.
6//! ## Architecture
7//!
8//! The relayer domain is organized into network-specific implementations
9//! that share common interfaces for transaction handling and monitoring.
10
11use 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
54// Re-export SwapResult from solana module for use in Stellar
55pub use solana::SwapResult;
56
57/// The `Relayer` trait defines the core functionality required for a relayer
58/// in the system. Implementors of this trait are responsible for handling
59/// transaction requests, managing balances, and interacting with the network.
60#[async_trait]
61#[cfg_attr(test, automock)]
62#[allow(dead_code)]
63pub trait Relayer {
64    /// Processes a transaction request and returns the result.
65    ///
66    /// # Arguments
67    ///
68    /// * `tx_request` - The transaction request to be processed.
69    ///
70    /// # Returns
71    ///
72    /// A `Result` containing a `TransactionRepoModel` on success, or a
73    /// `RelayerError` on failure.
74    async fn process_transaction_request(
75        &self,
76        tx_request: NetworkTransactionRequest,
77    ) -> Result<TransactionRepoModel, RelayerError>;
78
79    /// Retrieves the current balance of the relayer.
80    ///
81    /// # Returns
82    ///
83    /// A `Result` containing a `BalanceResponse` on success, or a
84    /// `RelayerError` on failure.
85    async fn get_balance(&self) -> Result<BalanceResponse, RelayerError>;
86
87    /// Deletes all pending transactions.
88    ///
89    /// # Returns
90    ///
91    /// A `Result` containing a `DeletePendingTransactionsResponse` with details
92    /// about which transactions were cancelled and which failed, or a `RelayerError` on failure.
93    async fn delete_pending_transactions(
94        &self,
95    ) -> Result<DeletePendingTransactionsResponse, RelayerError>;
96
97    /// Signs data using the relayer's credentials.
98    ///
99    /// # Arguments
100    ///
101    /// * `request` - The data to be signed.
102    ///
103    /// # Returns
104    ///
105    /// A `Result` containing a `SignDataResponse` on success, or a
106    /// `RelayerError` on failure.
107    async fn sign_data(&self, request: SignDataRequest) -> Result<SignDataResponse, RelayerError>;
108
109    /// Signs typed data using the relayer's credentials.
110    ///
111    /// # Arguments
112    ///
113    /// * `request` - The typed data to be signed.
114    ///
115    /// # Returns
116    ///
117    /// A `Result` containing a `SignDataResponse` on success, or a
118    /// `RelayerError` on failure.
119    async fn sign_typed_data(
120        &self,
121        request: SignTypedDataRequest,
122    ) -> Result<SignDataResponse, RelayerError>;
123
124    /// Executes a JSON-RPC request.
125    ///
126    /// # Arguments
127    ///
128    /// * `request` - The JSON-RPC request to be executed.
129    ///
130    /// # Returns
131    ///
132    /// A `Result` containing a `JsonRpcResponse` on success, or a
133    /// `RelayerError` on failure.
134    async fn rpc(
135        &self,
136        request: JsonRpcRequest<NetworkRpcRequest>,
137    ) -> Result<JsonRpcResponse<NetworkRpcResult>, RelayerError>;
138
139    /// Retrieves the current status of the relayer.
140    ///
141    /// # Returns
142    ///
143    /// A `Result` containing `RelayerStatus` on success, or a
144    /// `RelayerError` on failure.
145    async fn get_status(&self) -> Result<RelayerStatus, RelayerError>;
146
147    /// Initializes the relayer.
148    ///
149    /// # Returns
150    ///
151    /// A `Result` indicating success, or a `RelayerError` on failure.
152    async fn initialize_relayer(&self) -> Result<(), RelayerError>;
153
154    /// Runs health checks on the relayer without side effects.
155    ///
156    /// This method performs all necessary health checks (RPC validation, balance checks, etc.)
157    /// and returns the results without updating any state or sending notifications.
158    ///
159    /// # Returns
160    ///
161    /// * `Ok(())` - All health checks passed
162    /// * `Err(Vec<HealthCheckFailure>)` - One or more health checks failed with specific reasons
163    async fn check_health(&self) -> Result<(), Vec<crate::models::HealthCheckFailure>>;
164
165    /// Validates that the relayer's balance meets the minimum required.
166    ///
167    /// # Returns
168    ///
169    /// A `Result` indicating success, or a `RelayerError` on failure.
170    async fn validate_min_balance(&self) -> Result<(), RelayerError>;
171
172    /// Signs a transaction using the relayer's credentials.
173    ///
174    /// # Arguments
175    ///
176    /// * `unsigned_xdr` - The unsigned transaction XDR string to be signed.
177    ///
178    /// # Returns
179    ///
180    /// A `Result` containing a `SignTransactionExternalResponse` on success, or a
181    /// `RelayerError` on failure.
182    async fn sign_transaction(
183        &self,
184        request: &SignTransactionRequest,
185    ) -> Result<SignTransactionExternalResponse, RelayerError>;
186}
187
188/// Solana Relayer Dex Trait
189/// Subset of methods for Solana relayer
190#[async_trait]
191#[allow(dead_code)]
192#[cfg_attr(test, automock)]
193pub trait SolanaRelayerDexTrait {
194    /// Handles a token swap request.
195    async fn handle_token_swap_request(
196        &self,
197        relayer_id: String,
198    ) -> Result<Vec<SwapResult>, RelayerError>;
199}
200
201/// Subset of methods for Stellar relayer
202#[async_trait]
203#[allow(dead_code)]
204#[cfg_attr(test, automock)]
205pub trait StellarRelayerDexTrait {
206    /// Handles a token swap request.
207    async fn handle_token_swap_request(
208        &self,
209        relayer_id: String,
210    ) -> Result<Vec<SwapResult>, RelayerError>;
211}
212
213/// Gas abstraction trait for relayers that support fee estimation and transaction preparation.
214///
215/// This trait provides a REST-friendly interface for gas abstraction operations,
216/// allowing clients to estimate fees and prepare transactions without using JSON-RPC.
217#[async_trait]
218#[allow(dead_code)]
219#[cfg_attr(test, automock)]
220pub trait GasAbstractionTrait {
221    /// Gets a quote for a gasless transaction.
222    ///
223    /// # Arguments
224    ///
225    /// * `params` - The gasless transaction quote request parameters (network-agnostic).
226    ///
227    /// # Returns
228    ///
229    /// A `Result` containing a fee estimate result on success, or a `RelayerError` on failure.
230    async fn quote_sponsored_transaction(
231        &self,
232        params: SponsoredTransactionQuoteRequest,
233    ) -> Result<SponsoredTransactionQuoteResponse, RelayerError>;
234
235    /// Prepares a transaction with fee payments.
236    ///
237    /// # Arguments
238    ///
239    /// * `params` - The prepare transaction request parameters (network-agnostic).
240    ///
241    /// # Returns
242    ///
243    /// A `Result` containing a prepare transaction result on success, or a `RelayerError` on failure.
244    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    /// Handles a token swap request for supported networks (Solana and Stellar).
475    ///
476    /// # Returns
477    ///
478    /// A `Result` containing a `Vec<SwapResult>` on success, or a `RelayerError` on failure.
479    /// Returns `NotSupported` error for EVM networks.
480    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}