use std::collections::HashMap;
use std::time::Duration;
use alloy_chains::NamedChain;
use crate::types::config::MaxBlockRange;
pub mod constants;
#[derive(Debug, Clone)]
pub struct SemioscanConfig {
pub max_block_range: MaxBlockRange,
pub rate_limit_delay: Option<Duration>,
pub rpc_timeout: Duration,
pub serial_lookup_fallback_attempts: usize,
pub chain_overrides: HashMap<NamedChain, ChainConfig>,
}
#[derive(Debug, Clone, Default)]
pub struct ChainConfig {
pub max_block_range: Option<MaxBlockRange>,
pub rate_limit_delay: Option<Duration>,
pub rpc_timeout: Option<Duration>,
pub serial_lookup_fallback_attempts: Option<usize>,
}
impl Default for SemioscanConfig {
fn default() -> Self {
Self::with_common_defaults()
}
}
impl SemioscanConfig {
pub fn with_common_defaults() -> Self {
let mut config = Self {
max_block_range: MaxBlockRange::new(500),
rate_limit_delay: None,
rpc_timeout: Duration::from_secs(30), serial_lookup_fallback_attempts: 1,
chain_overrides: HashMap::new(),
};
config.set_chain_override(
NamedChain::Base,
ChainConfig {
max_block_range: None, rate_limit_delay: Some(Duration::from_millis(250)),
rpc_timeout: None, serial_lookup_fallback_attempts: None,
},
);
config.set_chain_override(
NamedChain::Sonic,
ChainConfig {
max_block_range: None,
rate_limit_delay: Some(Duration::from_millis(250)),
rpc_timeout: None, serial_lookup_fallback_attempts: None,
},
);
config
}
pub fn minimal() -> Self {
Self {
max_block_range: MaxBlockRange::new(500),
rate_limit_delay: None,
rpc_timeout: Duration::from_secs(30), serial_lookup_fallback_attempts: 1,
chain_overrides: HashMap::new(),
}
}
pub fn get_max_block_range(&self, chain: NamedChain) -> MaxBlockRange {
self.chain_overrides
.get(&chain)
.and_then(|c| c.max_block_range)
.unwrap_or(self.max_block_range)
}
pub fn get_rate_limit_delay(&self, chain: NamedChain) -> Option<Duration> {
self.chain_overrides
.get(&chain)
.and_then(|c| c.rate_limit_delay)
.or(self.rate_limit_delay)
}
pub fn get_rpc_timeout(&self, chain: NamedChain) -> Duration {
self.chain_overrides
.get(&chain)
.and_then(|c| c.rpc_timeout)
.unwrap_or(self.rpc_timeout)
}
#[must_use]
pub fn get_serial_lookup_fallback_attempts(&self, chain: NamedChain) -> usize {
self.chain_overrides
.get(&chain)
.and_then(|c| c.serial_lookup_fallback_attempts)
.unwrap_or(self.serial_lookup_fallback_attempts)
}
pub fn set_chain_override(&mut self, chain: NamedChain, config: ChainConfig) {
self.chain_overrides.insert(chain, config);
}
}
pub struct SemioscanConfigBuilder {
config: SemioscanConfig,
}
impl Default for SemioscanConfigBuilder {
fn default() -> Self {
Self::new()
}
}
impl SemioscanConfigBuilder {
pub fn new() -> Self {
Self {
config: SemioscanConfig::minimal(),
}
}
pub fn with_defaults() -> Self {
Self {
config: SemioscanConfig::with_common_defaults(),
}
}
pub fn max_block_range(mut self, max: u64) -> Self {
self.config.max_block_range = MaxBlockRange::new(max);
self
}
pub fn rate_limit_delay(mut self, delay: Duration) -> Self {
self.config.rate_limit_delay = Some(delay);
self
}
pub fn rpc_timeout(mut self, timeout: Duration) -> Self {
self.config.rpc_timeout = timeout;
self
}
pub fn serial_lookup_fallback_attempts(mut self, attempts: usize) -> Self {
self.config.serial_lookup_fallback_attempts = attempts;
self
}
pub fn chain_config(mut self, chain: NamedChain, config: ChainConfig) -> Self {
self.config.set_chain_override(chain, config);
self
}
pub fn chain_rate_limit(self, chain: NamedChain, delay: Duration) -> Self {
self.modify_chain(chain, |c| c.rate_limit_delay = Some(delay))
}
pub fn chain_max_blocks(self, chain: NamedChain, max: u64) -> Self {
self.modify_chain(chain, |c| c.max_block_range = Some(MaxBlockRange::new(max)))
}
pub fn chain_timeout(self, chain: NamedChain, timeout: Duration) -> Self {
self.modify_chain(chain, |c| c.rpc_timeout = Some(timeout))
}
pub fn chain_serial_lookup_fallback_attempts(self, chain: NamedChain, attempts: usize) -> Self {
self.modify_chain(chain, |c| {
c.serial_lookup_fallback_attempts = Some(attempts)
})
}
fn modify_chain<F: FnOnce(&mut ChainConfig)>(mut self, chain: NamedChain, f: F) -> Self {
f(self.config.chain_overrides.entry(chain).or_default());
self
}
pub fn build(self) -> SemioscanConfig {
self.config
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = SemioscanConfig::default();
assert_eq!(
config.get_rate_limit_delay(NamedChain::Base),
Some(Duration::from_millis(250))
);
assert_eq!(
config.get_rate_limit_delay(NamedChain::Sonic),
Some(Duration::from_millis(250))
);
assert_eq!(config.get_rate_limit_delay(NamedChain::Arbitrum), None);
assert_eq!(
config.get_max_block_range(NamedChain::Base),
MaxBlockRange::new(500)
);
assert_eq!(
config.get_max_block_range(NamedChain::Arbitrum),
MaxBlockRange::new(500)
);
assert_eq!(
config.get_serial_lookup_fallback_attempts(NamedChain::Base),
1
);
}
#[test]
fn test_minimal_config() {
let config = SemioscanConfig::minimal();
assert_eq!(config.get_rate_limit_delay(NamedChain::Base), None);
assert_eq!(config.get_rate_limit_delay(NamedChain::Sonic), None);
assert_eq!(
config.get_max_block_range(NamedChain::Base),
MaxBlockRange::new(500)
);
assert_eq!(
config.get_serial_lookup_fallback_attempts(NamedChain::Base),
1
);
}
#[test]
fn test_builder_pattern() {
let config = SemioscanConfigBuilder::new()
.max_block_range(1000)
.chain_rate_limit(NamedChain::Polygon, Duration::from_secs(1))
.build();
assert_eq!(
config.get_max_block_range(NamedChain::Polygon),
MaxBlockRange::new(1000)
);
assert_eq!(
config.get_rate_limit_delay(NamedChain::Polygon),
Some(Duration::from_secs(1))
);
}
#[test]
fn test_chain_override() {
let mut config = SemioscanConfig::minimal();
config.set_chain_override(
NamedChain::Arbitrum,
ChainConfig {
max_block_range: Some(MaxBlockRange::new(2000)),
rate_limit_delay: Some(Duration::from_millis(100)),
rpc_timeout: None, serial_lookup_fallback_attempts: None,
},
);
assert_eq!(
config.get_max_block_range(NamedChain::Arbitrum),
MaxBlockRange::new(2000)
);
assert_eq!(
config.get_max_block_range(NamedChain::Base),
MaxBlockRange::new(500)
); assert_eq!(
config.get_rate_limit_delay(NamedChain::Arbitrum),
Some(Duration::from_millis(100))
);
assert_eq!(
config.get_rpc_timeout(NamedChain::Arbitrum),
Duration::from_secs(30)
); assert_eq!(
config.get_serial_lookup_fallback_attempts(NamedChain::Arbitrum),
1
);
}
#[test]
fn test_builder_with_defaults() {
let config = SemioscanConfigBuilder::with_defaults()
.chain_max_blocks(NamedChain::Polygon, 1000)
.build();
assert_eq!(
config.get_rate_limit_delay(NamedChain::Base),
Some(Duration::from_millis(250))
);
assert_eq!(
config.get_rate_limit_delay(NamedChain::Sonic),
Some(Duration::from_millis(250))
);
assert_eq!(
config.get_max_block_range(NamedChain::Polygon),
MaxBlockRange::new(1000)
);
}
#[test]
fn test_chain_config_preserves_existing() {
let config = SemioscanConfigBuilder::new()
.chain_max_blocks(NamedChain::Arbitrum, 1000)
.chain_serial_lookup_fallback_attempts(NamedChain::Arbitrum, 2)
.chain_rate_limit(NamedChain::Arbitrum, Duration::from_millis(100))
.build();
assert_eq!(
config.get_max_block_range(NamedChain::Arbitrum),
MaxBlockRange::new(1000)
);
assert_eq!(
config.get_rate_limit_delay(NamedChain::Arbitrum),
Some(Duration::from_millis(100))
);
assert_eq!(
config.get_serial_lookup_fallback_attempts(NamedChain::Arbitrum),
2
);
}
#[test]
fn test_global_rate_limit() {
let config = SemioscanConfigBuilder::new()
.rate_limit_delay(Duration::from_millis(500))
.build();
assert_eq!(
config.get_rate_limit_delay(NamedChain::Arbitrum),
Some(Duration::from_millis(500))
);
assert_eq!(
config.get_rate_limit_delay(NamedChain::Base),
Some(Duration::from_millis(500))
);
}
#[test]
fn test_global_serial_lookup_fallback_attempts() {
let config = SemioscanConfigBuilder::new()
.serial_lookup_fallback_attempts(3)
.build();
assert_eq!(
config.get_serial_lookup_fallback_attempts(NamedChain::Arbitrum),
3
);
assert_eq!(
config.get_serial_lookup_fallback_attempts(NamedChain::Base),
3
);
}
#[test]
fn test_chain_override_global_serial_lookup_fallback_attempts() {
let config = SemioscanConfigBuilder::new()
.serial_lookup_fallback_attempts(3)
.chain_serial_lookup_fallback_attempts(NamedChain::ZkSync, 0)
.build();
assert_eq!(
config.get_serial_lookup_fallback_attempts(NamedChain::Arbitrum),
3
);
assert_eq!(
config.get_serial_lookup_fallback_attempts(NamedChain::ZkSync),
0
);
}
#[test]
fn test_chain_override_global_rate_limit() {
let config = SemioscanConfigBuilder::new()
.rate_limit_delay(Duration::from_millis(500))
.chain_rate_limit(NamedChain::Base, Duration::from_millis(250))
.build();
assert_eq!(
config.get_rate_limit_delay(NamedChain::Base),
Some(Duration::from_millis(250))
);
assert_eq!(
config.get_rate_limit_delay(NamedChain::Arbitrum),
Some(Duration::from_millis(500))
);
}
}