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}