cctp_rs/bridge/
config.rs

1// SPDX-FileCopyrightText: 2025 Semiotic AI, Inc.
2//
3// SPDX-License-Identifier: Apache-2.0
4
5/// Circle Iris API environment URLs
6///
7/// See <https://developers.circle.com/stablecoins/cctp-apis>
8///
9pub const IRIS_API: &str = "https://iris-api.circle.com";
10pub const IRIS_API_SANDBOX: &str = "https://iris-api-sandbox.circle.com";
11
12/// CCTP v1 attestation API path
13pub const ATTESTATION_PATH_V1: &str = "/v1/attestations/";
14
15/// CCTP v2 messages API path
16///
17/// V2 uses a different endpoint format than v1:
18/// - V1: `/v1/attestations/{messageHash}`
19/// - V2: `/v2/messages/{sourceDomain}?transactionHash={txHash}`
20pub const MESSAGES_PATH_V2: &str = "/v2/messages/";
21
22/// Configuration for attestation polling behavior.
23///
24/// Controls how the bridge polls Circle's Iris API for attestation availability.
25/// Use the builder methods to customize, or use preset configurations for common scenarios.
26///
27/// # Examples
28///
29/// ```rust
30/// use cctp_rs::PollingConfig;
31///
32/// // Use defaults (30 attempts, 60 second intervals)
33/// let config = PollingConfig::default();
34///
35/// // Customize polling behavior
36/// let config = PollingConfig::default()
37///     .with_max_attempts(20)
38///     .with_poll_interval_secs(30);
39///
40/// // Use preset for fast transfers (30 attempts, 5 second intervals)
41/// let config = PollingConfig::fast_transfer();
42/// ```
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub struct PollingConfig {
45    /// Maximum number of polling attempts before giving up.
46    pub max_attempts: u32,
47    /// Seconds to wait between polling attempts.
48    pub poll_interval_secs: u64,
49}
50
51impl Default for PollingConfig {
52    /// Creates a default polling configuration suitable for standard CCTP v1 transfers.
53    ///
54    /// - `max_attempts`: 30
55    /// - `poll_interval_secs`: 60 (1 minute)
56    ///
57    /// This results in a maximum wait time of ~30 minutes, which accommodates
58    /// the typical 13-19 minute attestation time for v1 transfers.
59    fn default() -> Self {
60        Self {
61            max_attempts: 30,
62            poll_interval_secs: 60,
63        }
64    }
65}
66
67impl PollingConfig {
68    /// Creates a polling configuration optimized for CCTP v2 fast transfers.
69    ///
70    /// - `max_attempts`: 30
71    /// - `poll_interval_secs`: 5
72    ///
73    /// Fast transfers typically complete in under 30 seconds, so this configuration
74    /// polls more frequently with shorter intervals.
75    pub fn fast_transfer() -> Self {
76        Self {
77            max_attempts: 30,
78            poll_interval_secs: 5,
79        }
80    }
81
82    /// Sets the maximum number of polling attempts.
83    ///
84    /// # Arguments
85    ///
86    /// * `attempts` - Maximum number of times to poll the attestation API
87    ///
88    /// # Example
89    ///
90    /// ```rust
91    /// use cctp_rs::PollingConfig;
92    ///
93    /// let config = PollingConfig::default().with_max_attempts(60);
94    /// assert_eq!(config.max_attempts, 60);
95    /// ```
96    pub fn with_max_attempts(mut self, attempts: u32) -> Self {
97        self.max_attempts = attempts;
98        self
99    }
100
101    /// Sets the interval between polling attempts in seconds.
102    ///
103    /// # Arguments
104    ///
105    /// * `secs` - Seconds to wait between each polling attempt
106    ///
107    /// # Example
108    ///
109    /// ```rust
110    /// use cctp_rs::PollingConfig;
111    ///
112    /// let config = PollingConfig::default().with_poll_interval_secs(30);
113    /// assert_eq!(config.poll_interval_secs, 30);
114    /// ```
115    pub fn with_poll_interval_secs(mut self, secs: u64) -> Self {
116        self.poll_interval_secs = secs;
117        self
118    }
119
120    /// Returns the total maximum wait time in seconds.
121    ///
122    /// This is calculated as `max_attempts * poll_interval_secs`.
123    ///
124    /// # Example
125    ///
126    /// ```rust
127    /// use cctp_rs::PollingConfig;
128    ///
129    /// let config = PollingConfig::default();
130    /// assert_eq!(config.total_timeout_secs(), 30 * 60); // 30 minutes
131    /// ```
132    pub fn total_timeout_secs(&self) -> u64 {
133        self.max_attempts as u64 * self.poll_interval_secs
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_default_config() {
143        let config = PollingConfig::default();
144        assert_eq!(config.max_attempts, 30);
145        assert_eq!(config.poll_interval_secs, 60);
146        assert_eq!(config.total_timeout_secs(), 1800); // 30 minutes
147    }
148
149    #[test]
150    fn test_fast_transfer_config() {
151        let config = PollingConfig::fast_transfer();
152        assert_eq!(config.max_attempts, 30);
153        assert_eq!(config.poll_interval_secs, 5);
154        assert_eq!(config.total_timeout_secs(), 150); // 2.5 minutes
155    }
156
157    #[test]
158    fn test_builder_methods() {
159        let config = PollingConfig::default()
160            .with_max_attempts(20)
161            .with_poll_interval_secs(30);
162        assert_eq!(config.max_attempts, 20);
163        assert_eq!(config.poll_interval_secs, 30);
164        assert_eq!(config.total_timeout_secs(), 600); // 10 minutes
165    }
166
167    #[test]
168    fn test_config_is_copy() {
169        let config = PollingConfig::default();
170        let copied = config;
171        assert_eq!(config, copied);
172    }
173}