use alloy_chains::NamedChain;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::error::{CctpError, Result};
pub const IRIS_API: &str = "https://iris-api.circle.com";
pub const IRIS_API_SANDBOX: &str = "https://iris-api-sandbox.circle.com";
pub fn iris_api_url(chain: NamedChain) -> Url {
if chain.is_testnet() {
Url::parse(IRIS_API_SANDBOX).unwrap()
} else {
Url::parse(IRIS_API).unwrap()
}
}
pub const ATTESTATION_PATH_V1: &str = "/v1/attestations/";
pub const MESSAGES_PATH_V2: &str = "/v2/messages/";
pub const TRANSFER_FEES_PATH_V2: &str = "/v2/burn/USDC/fees/";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct PollingConfig {
pub max_attempts: u32,
pub poll_interval_secs: u64,
}
impl Default for PollingConfig {
fn default() -> Self {
Self {
max_attempts: 30,
poll_interval_secs: 60,
}
}
}
impl PollingConfig {
pub fn fast_transfer() -> Self {
Self {
max_attempts: 30,
poll_interval_secs: 5,
}
}
pub fn try_new(max_attempts: u32, poll_interval_secs: u64) -> Result<Self> {
let config = Self {
max_attempts,
poll_interval_secs,
};
config.validate()?;
Ok(config)
}
pub fn validate(&self) -> Result<()> {
if self.max_attempts == 0 {
return Err(CctpError::InvalidConfig(
"PollingConfig.max_attempts must be greater than 0".to_string(),
));
}
if self.poll_interval_secs == 0 {
return Err(CctpError::InvalidConfig(
"PollingConfig.poll_interval_secs must be greater than 0".to_string(),
));
}
Ok(())
}
pub fn with_max_attempts(mut self, attempts: u32) -> Self {
self.max_attempts = attempts;
self
}
pub fn with_poll_interval_secs(mut self, secs: u64) -> Self {
self.poll_interval_secs = secs;
self
}
pub fn total_timeout_secs(&self) -> u64 {
u64::from(self.max_attempts).saturating_mul(self.poll_interval_secs)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = PollingConfig::default();
assert_eq!(config.max_attempts, 30);
assert_eq!(config.poll_interval_secs, 60);
assert_eq!(config.total_timeout_secs(), 1800); }
#[test]
fn test_fast_transfer_config() {
let config = PollingConfig::fast_transfer();
assert_eq!(config.max_attempts, 30);
assert_eq!(config.poll_interval_secs, 5);
assert_eq!(config.total_timeout_secs(), 150); }
#[test]
fn test_builder_methods() {
let config = PollingConfig::default()
.with_max_attempts(20)
.with_poll_interval_secs(30);
assert_eq!(config.max_attempts, 20);
assert_eq!(config.poll_interval_secs, 30);
assert_eq!(config.total_timeout_secs(), 600); }
#[test]
fn test_config_is_copy() {
let config = PollingConfig::default();
let copied = config;
assert_eq!(config, copied);
}
#[test]
fn test_validate_accepts_defaults() {
assert!(PollingConfig::default().validate().is_ok());
assert!(PollingConfig::fast_transfer().validate().is_ok());
}
#[test]
fn test_validate_rejects_zero_max_attempts() {
let config = PollingConfig::default().with_max_attempts(0);
let err = config.validate().unwrap_err();
assert!(
matches!(err, CctpError::InvalidConfig(ref msg) if msg.contains("max_attempts")),
"expected InvalidConfig mentioning max_attempts, got {err:?}"
);
}
#[test]
fn test_validate_rejects_zero_poll_interval() {
let config = PollingConfig::default().with_poll_interval_secs(0);
let err = config.validate().unwrap_err();
assert!(
matches!(err, CctpError::InvalidConfig(ref msg) if msg.contains("poll_interval_secs")),
"expected InvalidConfig mentioning poll_interval_secs, got {err:?}"
);
}
#[test]
fn test_try_new_accepts_positive_values() {
let config = PollingConfig::try_new(20, 30).unwrap();
assert_eq!(config.max_attempts, 20);
assert_eq!(config.poll_interval_secs, 30);
}
#[test]
fn test_try_new_rejects_zero_inputs() {
assert!(PollingConfig::try_new(0, 30).is_err());
assert!(PollingConfig::try_new(20, 0).is_err());
assert!(PollingConfig::try_new(0, 0).is_err());
}
#[test]
fn test_total_timeout_saturates_on_overflow() {
let config = PollingConfig {
max_attempts: u32::MAX,
poll_interval_secs: u64::MAX,
};
assert_eq!(config.total_timeout_secs(), u64::MAX);
}
#[test]
fn test_total_timeout_with_large_in_range_values() {
let config = PollingConfig {
max_attempts: 1_000_000,
poll_interval_secs: 3600,
};
assert_eq!(config.total_timeout_secs(), 3_600_000_000);
}
}