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