Skip to main content

aptos_sdk/
config.rs

1//! Network configuration for the Aptos SDK.
2//!
3//! This module provides configuration options for connecting to different
4//! Aptos networks (mainnet, testnet, devnet) or custom endpoints.
5
6use crate::error::{AptosError, AptosResult};
7use crate::retry::RetryConfig;
8use crate::types::ChainId;
9use std::time::Duration;
10use url::Url;
11
12/// Validates that a URL uses a safe scheme (http or https).
13///
14/// # Security
15///
16/// This prevents SSRF attacks via dangerous URL schemes like `file://`, `gopher://`, etc.
17/// For production use, HTTPS is strongly recommended. HTTP is allowed for localhost
18/// development only.
19fn validate_url_scheme(url: &Url) -> AptosResult<()> {
20    match url.scheme() {
21        "https" => Ok(()),
22        "http" => {
23            // HTTP is allowed but only recommended for localhost development
24            // Log a warning in the future if we add logging
25            Ok(())
26        }
27        scheme => Err(AptosError::Config(format!(
28            "unsupported URL scheme '{scheme}': only 'http' and 'https' are allowed"
29        ))),
30    }
31}
32
33/// Configuration for HTTP connection pooling.
34///
35/// Controls how connections are reused across requests for better performance.
36#[derive(Debug, Clone)]
37pub struct PoolConfig {
38    /// Maximum number of idle connections per host.
39    /// Default: unlimited (no limit)
40    pub max_idle_per_host: Option<usize>,
41    /// Maximum total idle connections in the pool.
42    /// Default: 100
43    pub max_idle_total: usize,
44    /// How long to keep idle connections alive.
45    /// Default: 90 seconds
46    pub idle_timeout: Duration,
47    /// Whether to enable TCP keepalive.
48    /// Default: true
49    pub tcp_keepalive: Option<Duration>,
50    /// Whether to enable TCP nodelay (disable Nagle's algorithm).
51    /// Default: true
52    pub tcp_nodelay: bool,
53    /// Maximum response body size in bytes.
54    /// Default: 100 MB (`104_857_600` bytes)
55    ///
56    /// # Security
57    ///
58    /// This limit helps prevent memory exhaustion from extremely large responses.
59    /// The Aptos API responses are typically much smaller than this limit.
60    pub max_response_size: usize,
61}
62
63/// Default maximum response size: 100 MB
64const DEFAULT_MAX_RESPONSE_SIZE: usize = 100 * 1024 * 1024;
65
66impl Default for PoolConfig {
67    fn default() -> Self {
68        Self {
69            max_idle_per_host: None, // unlimited
70            max_idle_total: 100,
71            idle_timeout: Duration::from_secs(90),
72            tcp_keepalive: Some(Duration::from_secs(60)),
73            tcp_nodelay: true,
74            max_response_size: DEFAULT_MAX_RESPONSE_SIZE,
75        }
76    }
77}
78
79impl PoolConfig {
80    /// Creates a new pool configuration builder.
81    pub fn builder() -> PoolConfigBuilder {
82        PoolConfigBuilder::default()
83    }
84
85    /// Creates a configuration optimized for high-throughput scenarios.
86    ///
87    /// - More idle connections
88    /// - Longer idle timeout
89    /// - TCP keepalive enabled
90    pub fn high_throughput() -> Self {
91        Self {
92            max_idle_per_host: Some(32),
93            max_idle_total: 256,
94            idle_timeout: Duration::from_secs(300),
95            tcp_keepalive: Some(Duration::from_secs(30)),
96            tcp_nodelay: true,
97            max_response_size: DEFAULT_MAX_RESPONSE_SIZE,
98        }
99    }
100
101    /// Creates a configuration optimized for low-latency scenarios.
102    ///
103    /// - Fewer idle connections (fresher connections)
104    /// - Shorter idle timeout
105    /// - TCP nodelay enabled
106    pub fn low_latency() -> Self {
107        Self {
108            max_idle_per_host: Some(8),
109            max_idle_total: 32,
110            idle_timeout: Duration::from_secs(30),
111            tcp_keepalive: Some(Duration::from_secs(15)),
112            tcp_nodelay: true,
113            max_response_size: DEFAULT_MAX_RESPONSE_SIZE,
114        }
115    }
116
117    /// Creates a minimal configuration for constrained environments.
118    ///
119    /// - Minimal idle connections
120    /// - Short idle timeout
121    pub fn minimal() -> Self {
122        Self {
123            max_idle_per_host: Some(2),
124            max_idle_total: 8,
125            idle_timeout: Duration::from_secs(10),
126            tcp_keepalive: None,
127            tcp_nodelay: true,
128            max_response_size: DEFAULT_MAX_RESPONSE_SIZE,
129        }
130    }
131}
132
133/// Builder for `PoolConfig`.
134#[derive(Debug, Clone, Default)]
135#[allow(clippy::option_option)] // Intentional: distinguishes "not set" from "explicitly set to None"
136pub struct PoolConfigBuilder {
137    max_idle_per_host: Option<usize>,
138    max_idle_total: Option<usize>,
139    idle_timeout: Option<Duration>,
140    /// None = not set (use default), Some(None) = explicitly disabled, Some(Some(d)) = explicitly set
141    tcp_keepalive: Option<Option<Duration>>,
142    tcp_nodelay: Option<bool>,
143    max_response_size: Option<usize>,
144}
145
146impl PoolConfigBuilder {
147    /// Sets the maximum idle connections per host.
148    #[must_use]
149    pub fn max_idle_per_host(mut self, max: usize) -> Self {
150        self.max_idle_per_host = Some(max);
151        self
152    }
153
154    /// Removes the limit on idle connections per host.
155    #[must_use]
156    pub fn unlimited_idle_per_host(mut self) -> Self {
157        self.max_idle_per_host = None;
158        self
159    }
160
161    /// Sets the maximum total idle connections.
162    #[must_use]
163    pub fn max_idle_total(mut self, max: usize) -> Self {
164        self.max_idle_total = Some(max);
165        self
166    }
167
168    /// Sets the idle connection timeout.
169    #[must_use]
170    pub fn idle_timeout(mut self, timeout: Duration) -> Self {
171        self.idle_timeout = Some(timeout);
172        self
173    }
174
175    /// Sets the TCP keepalive interval.
176    #[must_use]
177    pub fn tcp_keepalive(mut self, interval: Duration) -> Self {
178        self.tcp_keepalive = Some(Some(interval));
179        self
180    }
181
182    /// Disables TCP keepalive.
183    #[must_use]
184    pub fn no_tcp_keepalive(mut self) -> Self {
185        self.tcp_keepalive = Some(None);
186        self
187    }
188
189    /// Sets whether to enable TCP nodelay.
190    #[must_use]
191    pub fn tcp_nodelay(mut self, enabled: bool) -> Self {
192        self.tcp_nodelay = Some(enabled);
193        self
194    }
195
196    /// Sets the maximum response body size in bytes.
197    ///
198    /// # Security
199    ///
200    /// This helps prevent memory exhaustion from extremely large responses.
201    #[must_use]
202    pub fn max_response_size(mut self, size: usize) -> Self {
203        self.max_response_size = Some(size);
204        self
205    }
206
207    /// Builds the pool configuration.
208    pub fn build(self) -> PoolConfig {
209        let default = PoolConfig::default();
210        PoolConfig {
211            max_idle_per_host: self.max_idle_per_host.or(default.max_idle_per_host),
212            max_idle_total: self.max_idle_total.unwrap_or(default.max_idle_total),
213            idle_timeout: self.idle_timeout.unwrap_or(default.idle_timeout),
214            tcp_keepalive: self.tcp_keepalive.unwrap_or(default.tcp_keepalive),
215            tcp_nodelay: self.tcp_nodelay.unwrap_or(default.tcp_nodelay),
216            max_response_size: self.max_response_size.unwrap_or(default.max_response_size),
217        }
218    }
219}
220
221/// Configuration for the Aptos client.
222///
223/// Use the builder methods to customize the configuration, or use one of the
224/// preset configurations like [`AptosConfig::mainnet()`], [`AptosConfig::testnet()`],
225/// or [`AptosConfig::devnet()`].
226///
227/// # Example
228///
229/// ```rust
230/// use aptos_sdk::AptosConfig;
231/// use aptos_sdk::retry::RetryConfig;
232/// use aptos_sdk::config::PoolConfig;
233///
234/// // Use testnet with default settings
235/// let config = AptosConfig::testnet();
236///
237/// // Custom configuration with retry and connection pooling
238/// let config = AptosConfig::testnet()
239///     .with_timeout(std::time::Duration::from_secs(30))
240///     .with_retry(RetryConfig::aggressive())
241///     .with_pool(PoolConfig::high_throughput());
242/// ```
243#[derive(Debug, Clone)]
244pub struct AptosConfig {
245    /// The network to connect to
246    pub(crate) network: Network,
247    /// REST API URL (fullnode)
248    pub(crate) fullnode_url: Url,
249    /// Indexer GraphQL URL (optional)
250    pub(crate) indexer_url: Option<Url>,
251    /// Faucet URL (optional, for testnets)
252    pub(crate) faucet_url: Option<Url>,
253    /// Request timeout
254    pub(crate) timeout: Duration,
255    /// Retry configuration for transient failures
256    pub(crate) retry_config: RetryConfig,
257    /// Connection pool configuration
258    pub(crate) pool_config: PoolConfig,
259    /// Optional API key for authenticated access
260    pub(crate) api_key: Option<String>,
261}
262
263/// Known Aptos networks.
264#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
265pub enum Network {
266    /// Aptos mainnet
267    Mainnet,
268    /// Aptos testnet
269    Testnet,
270    /// Aptos devnet
271    Devnet,
272    /// Local development network
273    Local,
274    /// Custom network
275    Custom,
276}
277
278impl Network {
279    /// Returns the chain ID for this network.
280    pub fn chain_id(&self) -> ChainId {
281        match self {
282            Network::Mainnet => ChainId::mainnet(),
283            Network::Testnet => ChainId::testnet(),
284            Network::Devnet => ChainId::new(165), // Devnet chain ID
285            Network::Local => ChainId::new(4),    // Local testing chain ID
286            Network::Custom => ChainId::new(0),   // Must be set manually
287        }
288    }
289
290    /// Returns the network name as a string.
291    pub fn as_str(&self) -> &'static str {
292        match self {
293            Network::Mainnet => "mainnet",
294            Network::Testnet => "testnet",
295            Network::Devnet => "devnet",
296            Network::Local => "local",
297            Network::Custom => "custom",
298        }
299    }
300}
301
302impl Default for AptosConfig {
303    fn default() -> Self {
304        Self::devnet()
305    }
306}
307
308impl AptosConfig {
309    /// Creates a configuration for Aptos mainnet.
310    ///
311    /// # Example
312    ///
313    /// ```rust
314    /// use aptos_sdk::AptosConfig;
315    ///
316    /// let config = AptosConfig::mainnet();
317    /// ```
318    #[allow(clippy::missing_panics_doc)]
319    #[must_use]
320    pub fn mainnet() -> Self {
321        Self {
322            network: Network::Mainnet,
323            fullnode_url: Url::parse("https://fullnode.mainnet.aptoslabs.com/v1")
324                .expect("valid mainnet URL"),
325            indexer_url: Some(
326                Url::parse("https://indexer.mainnet.aptoslabs.com/v1/graphql")
327                    .expect("valid indexer URL"),
328            ),
329            faucet_url: None, // No faucet on mainnet
330            timeout: Duration::from_secs(30),
331            retry_config: RetryConfig::conservative(), // More conservative for mainnet
332            pool_config: PoolConfig::default(),
333            api_key: None,
334        }
335    }
336
337    /// Creates a configuration for Aptos testnet.
338    ///
339    /// # Example
340    ///
341    /// ```rust
342    /// use aptos_sdk::AptosConfig;
343    ///
344    /// let config = AptosConfig::testnet();
345    /// ```
346    #[allow(clippy::missing_panics_doc)]
347    #[must_use]
348    pub fn testnet() -> Self {
349        Self {
350            network: Network::Testnet,
351            fullnode_url: Url::parse("https://fullnode.testnet.aptoslabs.com/v1")
352                .expect("valid testnet URL"),
353            indexer_url: Some(
354                Url::parse("https://indexer.testnet.aptoslabs.com/v1/graphql")
355                    .expect("valid indexer URL"),
356            ),
357            faucet_url: Some(
358                Url::parse("https://faucet.testnet.aptoslabs.com").expect("valid faucet URL"),
359            ),
360            timeout: Duration::from_secs(30),
361            retry_config: RetryConfig::default(),
362            pool_config: PoolConfig::default(),
363            api_key: None,
364        }
365    }
366
367    /// Creates a configuration for Aptos devnet.
368    ///
369    /// # Example
370    ///
371    /// ```rust
372    /// use aptos_sdk::AptosConfig;
373    ///
374    /// let config = AptosConfig::devnet();
375    /// ```
376    #[allow(clippy::missing_panics_doc)]
377    #[must_use]
378    pub fn devnet() -> Self {
379        Self {
380            network: Network::Devnet,
381            fullnode_url: Url::parse("https://fullnode.devnet.aptoslabs.com/v1")
382                .expect("valid devnet URL"),
383            indexer_url: Some(
384                Url::parse("https://indexer.devnet.aptoslabs.com/v1/graphql")
385                    .expect("valid indexer URL"),
386            ),
387            faucet_url: Some(
388                Url::parse("https://faucet.devnet.aptoslabs.com").expect("valid faucet URL"),
389            ),
390            timeout: Duration::from_secs(30),
391            retry_config: RetryConfig::default(),
392            pool_config: PoolConfig::default(),
393            api_key: None,
394        }
395    }
396
397    /// Creates a configuration for a local development network.
398    ///
399    /// This assumes the local network is running on the default ports
400    /// (REST API on 8080, faucet on 8081).
401    ///
402    /// # Example
403    ///
404    /// ```rust
405    /// use aptos_sdk::AptosConfig;
406    ///
407    /// let config = AptosConfig::local();
408    /// ```
409    #[allow(clippy::missing_panics_doc)]
410    #[must_use]
411    pub fn local() -> Self {
412        Self {
413            network: Network::Local,
414            fullnode_url: Url::parse("http://127.0.0.1:8080/v1").expect("valid local URL"),
415            indexer_url: None,
416            faucet_url: Some(Url::parse("http://127.0.0.1:8081").expect("valid local faucet URL")),
417            timeout: Duration::from_secs(10),
418            retry_config: RetryConfig::aggressive(), // Fast retries for local dev
419            pool_config: PoolConfig::low_latency(),  // Low latency for local dev
420            api_key: None,
421        }
422    }
423
424    /// Creates a custom configuration with the specified fullnode URL.
425    ///
426    /// # Security
427    ///
428    /// Only `http://` and `https://` URL schemes are allowed. Using `https://` is
429    /// strongly recommended for production. HTTP is acceptable only for localhost
430    /// development environments.
431    ///
432    /// # Errors
433    ///
434    /// Returns an error if the `fullnode_url` cannot be parsed as a valid URL
435    /// or uses an unsupported scheme (e.g., `file://`, `ftp://`).
436    ///
437    /// # Example
438    ///
439    /// ```rust
440    /// use aptos_sdk::AptosConfig;
441    ///
442    /// let config = AptosConfig::custom("https://my-node.example.com/v1").unwrap();
443    /// ```
444    pub fn custom(fullnode_url: &str) -> AptosResult<Self> {
445        let url = Url::parse(fullnode_url)?;
446        validate_url_scheme(&url)?;
447        Ok(Self {
448            network: Network::Custom,
449            fullnode_url: url,
450            indexer_url: None,
451            faucet_url: None,
452            timeout: Duration::from_secs(30),
453            retry_config: RetryConfig::default(),
454            pool_config: PoolConfig::default(),
455            api_key: None,
456        })
457    }
458
459    /// Sets the request timeout.
460    #[must_use]
461    pub fn with_timeout(mut self, timeout: Duration) -> Self {
462        self.timeout = timeout;
463        self
464    }
465
466    /// Sets the retry configuration for transient failures.
467    ///
468    /// # Example
469    ///
470    /// ```rust
471    /// use aptos_sdk::AptosConfig;
472    /// use aptos_sdk::retry::RetryConfig;
473    ///
474    /// let config = AptosConfig::testnet()
475    ///     .with_retry(RetryConfig::aggressive());
476    /// ```
477    #[must_use]
478    pub fn with_retry(mut self, retry_config: RetryConfig) -> Self {
479        self.retry_config = retry_config;
480        self
481    }
482
483    /// Disables automatic retry for API calls.
484    ///
485    /// This is equivalent to `with_retry(RetryConfig::no_retry())`.
486    #[must_use]
487    pub fn without_retry(mut self) -> Self {
488        self.retry_config = RetryConfig::no_retry();
489        self
490    }
491
492    /// Sets the maximum number of retries for transient failures.
493    ///
494    /// This is a convenience method that modifies the retry config.
495    #[must_use]
496    pub fn with_max_retries(mut self, max_retries: u32) -> Self {
497        self.retry_config = RetryConfig::builder()
498            .max_retries(max_retries)
499            .initial_delay_ms(self.retry_config.initial_delay_ms)
500            .max_delay_ms(self.retry_config.max_delay_ms)
501            .exponential_base(self.retry_config.exponential_base)
502            .jitter(self.retry_config.jitter)
503            .build();
504        self
505    }
506
507    /// Sets the connection pool configuration.
508    ///
509    /// # Example
510    ///
511    /// ```rust
512    /// use aptos_sdk::AptosConfig;
513    /// use aptos_sdk::config::PoolConfig;
514    ///
515    /// let config = AptosConfig::testnet()
516    ///     .with_pool(PoolConfig::high_throughput());
517    /// ```
518    #[must_use]
519    pub fn with_pool(mut self, pool_config: PoolConfig) -> Self {
520        self.pool_config = pool_config;
521        self
522    }
523
524    /// Sets an API key for authenticated access.
525    ///
526    /// This is useful when using Aptos Build or other services that
527    /// provide higher rate limits with API keys.
528    #[must_use]
529    pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
530        self.api_key = Some(api_key.into());
531        self
532    }
533
534    /// Sets a custom indexer URL.
535    ///
536    /// # Security
537    ///
538    /// Only `http://` and `https://` URL schemes are allowed.
539    ///
540    /// # Errors
541    ///
542    /// Returns an error if the `url` cannot be parsed as a valid URL
543    /// or uses an unsupported scheme.
544    pub fn with_indexer_url(mut self, url: &str) -> AptosResult<Self> {
545        let parsed = Url::parse(url)?;
546        validate_url_scheme(&parsed)?;
547        self.indexer_url = Some(parsed);
548        Ok(self)
549    }
550
551    /// Sets a custom faucet URL.
552    ///
553    /// # Security
554    ///
555    /// Only `http://` and `https://` URL schemes are allowed.
556    ///
557    /// # Errors
558    ///
559    /// Returns an error if the `url` cannot be parsed as a valid URL
560    /// or uses an unsupported scheme.
561    pub fn with_faucet_url(mut self, url: &str) -> AptosResult<Self> {
562        let parsed = Url::parse(url)?;
563        validate_url_scheme(&parsed)?;
564        self.faucet_url = Some(parsed);
565        Ok(self)
566    }
567
568    /// Returns the network this config is for.
569    pub fn network(&self) -> Network {
570        self.network
571    }
572
573    /// Returns the fullnode URL.
574    pub fn fullnode_url(&self) -> &Url {
575        &self.fullnode_url
576    }
577
578    /// Returns the indexer URL, if configured.
579    pub fn indexer_url(&self) -> Option<&Url> {
580        self.indexer_url.as_ref()
581    }
582
583    /// Returns the faucet URL, if configured.
584    pub fn faucet_url(&self) -> Option<&Url> {
585        self.faucet_url.as_ref()
586    }
587
588    /// Returns the chain ID for this configuration.
589    pub fn chain_id(&self) -> ChainId {
590        self.network.chain_id()
591    }
592
593    /// Returns the retry configuration.
594    pub fn retry_config(&self) -> &RetryConfig {
595        &self.retry_config
596    }
597
598    /// Returns the request timeout.
599    pub fn timeout(&self) -> Duration {
600        self.timeout
601    }
602
603    /// Returns the connection pool configuration.
604    pub fn pool_config(&self) -> &PoolConfig {
605        &self.pool_config
606    }
607}
608
609#[cfg(test)]
610mod tests {
611    use super::*;
612
613    #[test]
614    fn test_mainnet_config() {
615        let config = AptosConfig::mainnet();
616        assert_eq!(config.network(), Network::Mainnet);
617        assert!(config.fullnode_url().as_str().contains("mainnet"));
618        assert!(config.faucet_url().is_none());
619    }
620
621    #[test]
622    fn test_testnet_config() {
623        let config = AptosConfig::testnet();
624        assert_eq!(config.network(), Network::Testnet);
625        assert!(config.fullnode_url().as_str().contains("testnet"));
626        assert!(config.faucet_url().is_some());
627    }
628
629    #[test]
630    fn test_devnet_config() {
631        let config = AptosConfig::devnet();
632        assert_eq!(config.network(), Network::Devnet);
633        assert!(config.fullnode_url().as_str().contains("devnet"));
634        assert!(config.faucet_url().is_some());
635        assert!(config.indexer_url().is_some());
636    }
637
638    #[test]
639    fn test_local_config() {
640        let config = AptosConfig::local();
641        assert_eq!(config.network(), Network::Local);
642        assert!(config.fullnode_url().as_str().contains("127.0.0.1"));
643        assert!(config.faucet_url().is_some());
644        assert!(config.indexer_url().is_none());
645    }
646
647    #[test]
648    fn test_custom_config() {
649        let config = AptosConfig::custom("https://custom.example.com/v1").unwrap();
650        assert_eq!(config.network(), Network::Custom);
651        assert_eq!(
652            config.fullnode_url().as_str(),
653            "https://custom.example.com/v1"
654        );
655    }
656
657    #[test]
658    fn test_custom_config_invalid_url() {
659        let result = AptosConfig::custom("not a valid url");
660        assert!(result.is_err());
661    }
662
663    #[test]
664    fn test_builder_methods() {
665        let config = AptosConfig::testnet()
666            .with_timeout(Duration::from_secs(60))
667            .with_max_retries(5)
668            .with_api_key("test-key");
669
670        assert_eq!(config.timeout, Duration::from_secs(60));
671        assert_eq!(config.retry_config.max_retries, 5);
672        assert_eq!(config.api_key, Some("test-key".to_string()));
673    }
674
675    #[test]
676    fn test_retry_config() {
677        let config = AptosConfig::testnet().with_retry(RetryConfig::aggressive());
678
679        assert_eq!(config.retry_config.max_retries, 5);
680        assert_eq!(config.retry_config.initial_delay_ms, 50);
681
682        let config = AptosConfig::testnet().without_retry();
683        assert_eq!(config.retry_config.max_retries, 0);
684    }
685
686    #[test]
687    fn test_network_retry_defaults() {
688        // Mainnet should be conservative
689        let mainnet = AptosConfig::mainnet();
690        assert_eq!(mainnet.retry_config.max_retries, 3);
691
692        // Local should be aggressive
693        let local = AptosConfig::local();
694        assert_eq!(local.retry_config.max_retries, 5);
695    }
696
697    #[test]
698    fn test_pool_config_default() {
699        let config = PoolConfig::default();
700        assert_eq!(config.max_idle_total, 100);
701        assert_eq!(config.idle_timeout, Duration::from_secs(90));
702        assert!(config.tcp_nodelay);
703    }
704
705    #[test]
706    fn test_pool_config_presets() {
707        let high = PoolConfig::high_throughput();
708        assert_eq!(high.max_idle_per_host, Some(32));
709        assert_eq!(high.max_idle_total, 256);
710
711        let low = PoolConfig::low_latency();
712        assert_eq!(low.max_idle_per_host, Some(8));
713        assert_eq!(low.idle_timeout, Duration::from_secs(30));
714
715        let minimal = PoolConfig::minimal();
716        assert_eq!(minimal.max_idle_per_host, Some(2));
717        assert_eq!(minimal.max_idle_total, 8);
718    }
719
720    #[test]
721    fn test_pool_config_builder() {
722        let config = PoolConfig::builder()
723            .max_idle_per_host(16)
724            .max_idle_total(64)
725            .idle_timeout(Duration::from_secs(60))
726            .tcp_nodelay(false)
727            .build();
728
729        assert_eq!(config.max_idle_per_host, Some(16));
730        assert_eq!(config.max_idle_total, 64);
731        assert_eq!(config.idle_timeout, Duration::from_secs(60));
732        assert!(!config.tcp_nodelay);
733    }
734
735    #[test]
736    fn test_pool_config_builder_tcp_keepalive() {
737        let config = PoolConfig::builder()
738            .tcp_keepalive(Duration::from_secs(30))
739            .build();
740        assert_eq!(config.tcp_keepalive, Some(Duration::from_secs(30)));
741
742        let config = PoolConfig::builder().no_tcp_keepalive().build();
743        assert_eq!(config.tcp_keepalive, None);
744    }
745
746    #[test]
747    fn test_pool_config_builder_unlimited_idle() {
748        let config = PoolConfig::builder().unlimited_idle_per_host().build();
749        assert_eq!(config.max_idle_per_host, None);
750    }
751
752    #[test]
753    fn test_aptos_config_with_pool() {
754        let config = AptosConfig::testnet().with_pool(PoolConfig::high_throughput());
755
756        assert_eq!(config.pool_config.max_idle_total, 256);
757    }
758
759    #[test]
760    fn test_aptos_config_with_indexer_url() {
761        let config = AptosConfig::testnet()
762            .with_indexer_url("https://custom-indexer.example.com/graphql")
763            .unwrap();
764        assert_eq!(
765            config.indexer_url().unwrap().as_str(),
766            "https://custom-indexer.example.com/graphql"
767        );
768    }
769
770    #[test]
771    fn test_aptos_config_with_faucet_url() {
772        let config = AptosConfig::mainnet()
773            .with_faucet_url("https://custom-faucet.example.com")
774            .unwrap();
775        assert_eq!(
776            config.faucet_url().unwrap().as_str(),
777            "https://custom-faucet.example.com/"
778        );
779    }
780
781    #[test]
782    fn test_aptos_config_default() {
783        let config = AptosConfig::default();
784        assert_eq!(config.network(), Network::Devnet);
785    }
786
787    #[test]
788    fn test_network_chain_id() {
789        assert_eq!(Network::Mainnet.chain_id().id(), 1);
790        assert_eq!(Network::Testnet.chain_id().id(), 2);
791        assert_eq!(Network::Devnet.chain_id().id(), 165);
792        assert_eq!(Network::Local.chain_id().id(), 4);
793        assert_eq!(Network::Custom.chain_id().id(), 0);
794    }
795
796    #[test]
797    fn test_network_as_str() {
798        assert_eq!(Network::Mainnet.as_str(), "mainnet");
799        assert_eq!(Network::Testnet.as_str(), "testnet");
800        assert_eq!(Network::Devnet.as_str(), "devnet");
801        assert_eq!(Network::Local.as_str(), "local");
802        assert_eq!(Network::Custom.as_str(), "custom");
803    }
804
805    #[test]
806    fn test_aptos_config_getters() {
807        let config = AptosConfig::testnet();
808
809        assert_eq!(config.timeout(), Duration::from_secs(30));
810        assert!(config.retry_config().max_retries > 0);
811        assert!(config.pool_config().max_idle_total > 0);
812        assert_eq!(config.chain_id().id(), 2);
813    }
814}