openzeppelin_relayer/models/network/stellar/
network.rs

1use crate::models::{NetworkConfigData, NetworkRepoModel, RepositoryError};
2use core::time::Duration;
3use serde::{Deserialize, Serialize};
4use sha2::{Digest, Sha256};
5use soroban_rs::xdr::Hash;
6
7#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
8pub struct StellarNetwork {
9    /// Unique network identifier (e.g., "mainnet", "devnet", "custom-devnet").
10    pub network: String,
11    /// List of RPC endpoint URLs for connecting to the network.
12    pub rpc_urls: Vec<String>,
13    /// List of Explorer endpoint URLs for connecting to the network.
14    pub explorer_urls: Option<Vec<String>>,
15    /// Estimated average time between blocks in milliseconds.
16    pub average_blocktime_ms: u64,
17    /// Flag indicating if the network is a testnet.
18    pub is_testnet: bool,
19    /// List of arbitrary tags for categorizing or filtering networks.
20    pub tags: Vec<String>,
21    /// The passphrase for the Stellar network.
22    pub passphrase: String,
23    /// The Horizon API URL for the Stellar network.
24    pub horizon_url: Option<String>,
25}
26
27impl TryFrom<NetworkRepoModel> for StellarNetwork {
28    type Error = RepositoryError;
29
30    /// Converts a NetworkRepoModel to a StellarNetwork.
31    ///
32    /// # Arguments
33    /// * `network_repo` - The repository model to convert
34    ///
35    /// # Returns
36    /// Result containing the StellarNetwork if successful, or a RepositoryError
37    fn try_from(network_repo: NetworkRepoModel) -> Result<Self, Self::Error> {
38        match &network_repo.config {
39            NetworkConfigData::Stellar(stellar_config) => {
40                let common = &stellar_config.common;
41
42                let rpc_urls = common.rpc_urls.clone().ok_or_else(|| {
43                    RepositoryError::InvalidData(format!(
44                        "Stellar network '{}' has no rpc_urls",
45                        network_repo.name
46                    ))
47                })?;
48
49                let average_blocktime_ms = common.average_blocktime_ms.ok_or_else(|| {
50                    RepositoryError::InvalidData(format!(
51                        "Stellar network '{}' has no average_blocktime_ms",
52                        network_repo.name
53                    ))
54                })?;
55
56                let passphrase = stellar_config.passphrase.clone().ok_or_else(|| {
57                    RepositoryError::InvalidData(format!(
58                        "Stellar network '{}' has no passphrase",
59                        network_repo.name
60                    ))
61                })?;
62
63                Ok(StellarNetwork {
64                    network: common.network.clone(),
65                    rpc_urls,
66                    explorer_urls: common.explorer_urls.clone(),
67                    average_blocktime_ms,
68                    is_testnet: common.is_testnet.unwrap_or(false),
69                    tags: common.tags.clone().unwrap_or_default(),
70                    passphrase,
71                    horizon_url: stellar_config.horizon_url.clone(),
72                })
73            }
74            _ => Err(RepositoryError::InvalidData(format!(
75                "Network '{}' is not a Stellar network",
76                network_repo.name
77            ))),
78        }
79    }
80}
81
82impl StellarNetwork {
83    pub fn network_id(&self) -> Hash {
84        let passphrase = self.passphrase.clone();
85        let hash_bytes: [u8; 32] = Sha256::digest(passphrase.as_bytes()).into();
86        Hash(hash_bytes)
87    }
88
89    pub fn average_blocktime(&self) -> Option<Duration> {
90        Some(Duration::from_millis(self.average_blocktime_ms))
91    }
92
93    pub fn public_rpc_urls(&self) -> Option<&[String]> {
94        if self.rpc_urls.is_empty() {
95            None
96        } else {
97            Some(&self.rpc_urls)
98        }
99    }
100
101    pub fn explorer_urls(&self) -> Option<&[String]> {
102        self.explorer_urls.as_deref()
103    }
104
105    pub fn is_testnet(&self) -> bool {
106        self.is_testnet
107    }
108}