use std::time::Duration;
use serde::{Deserialize, Serialize};
pub const DEFAULT_TARGET_BLOCK_TIME_SECS: u64 = 600;
#[derive(Debug, Clone)]
pub struct FeeEstimationConfig {
pub fallback_sat_per_vb: f64,
pub cache_ttl_secs: u64,
pub quote_max_input_count: usize,
pub quote_fixed_safety_sat: u64,
pub quote_safety_multiplier: f64,
}
impl Default for FeeEstimationConfig {
fn default() -> Self {
Self {
fallback_sat_per_vb: 2.0,
cache_ttl_secs: 60,
quote_max_input_count: 24,
quote_fixed_safety_sat: 500,
quote_safety_multiplier: 1.25,
}
}
}
#[derive(Debug, Clone)]
pub struct BatchConfig {
pub poll_interval: Duration,
pub max_batch_size: usize,
pub target_block_time: Duration,
pub standard_deadline: Duration,
pub economy_deadline: Duration,
pub max_intent_age: Option<Duration>,
pub fee_options: Vec<PaymentTier>,
pub fee_estimation: FeeEstimationConfig,
}
impl Default for BatchConfig {
fn default() -> Self {
let poll_interval = Duration::from_secs(30);
let target_block_time = Duration::from_secs(DEFAULT_TARGET_BLOCK_TIME_SECS);
let standard_deadline =
Self::deadline_for_target_blocks(PaymentTier::Standard, target_block_time);
let economy_deadline =
Self::deadline_for_target_blocks(PaymentTier::Economy, target_block_time);
Self {
poll_interval,
max_batch_size: 50,
target_block_time,
standard_deadline,
economy_deadline,
max_intent_age: Some(economy_deadline.saturating_add(poll_interval)),
fee_options: vec![PaymentTier::Immediate],
fee_estimation: FeeEstimationConfig::default(),
}
}
}
impl BatchConfig {
pub fn deadline_for_target_blocks(tier: PaymentTier, target_block_time: Duration) -> Duration {
Duration::from_secs(
target_block_time
.as_secs()
.saturating_mul(u64::from(tier.estimated_blocks())),
)
}
pub fn validate(&self) -> Result<(), String> {
if self.target_block_time.is_zero() {
return Err("BDK batch_config.target_block_time must be greater than zero".to_string());
}
if !self.fee_estimation.fallback_sat_per_vb.is_finite()
|| self.fee_estimation.fallback_sat_per_vb <= 0.0
|| self.fee_estimation.fallback_sat_per_vb.ceil() > f64::from(u32::MAX)
{
return Err(
"BDK batch_config.fee_estimation.fallback_sat_per_vb must be finite, greater than zero, and fit in u32 after rounding"
.to_string(),
);
}
validate_fee_options(&self.fee_options)
}
pub fn tier_for_fee_index(&self, fee_index: Option<u32>) -> Result<PaymentTier, u32> {
let Some(fee_index) = fee_index else {
return Ok(PaymentTier::Immediate);
};
self.fee_options
.get(fee_index as usize)
.copied()
.ok_or(fee_index)
}
}
#[derive(Debug, Clone)]
pub struct SyncConfig {
pub apply_chunk_size: usize,
pub lock_hold_warn_ms: u64,
}
impl Default for SyncConfig {
fn default() -> Self {
Self {
apply_chunk_size: 16,
lock_hold_warn_ms: 500,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub enum PaymentTier {
#[default]
Immediate,
Standard,
Economy,
}
impl PaymentTier {
pub fn from_config_name(s: &str) -> Option<Self> {
match s.trim().to_ascii_lowercase().as_str() {
"immediate" => Some(Self::Immediate),
"standard" => Some(Self::Standard),
"economy" => Some(Self::Economy),
_ => None,
}
}
pub fn config_name(self) -> &'static str {
match self {
Self::Immediate => "immediate",
Self::Standard => "standard",
Self::Economy => "economy",
}
}
pub fn estimated_blocks(self) -> u32 {
match self {
Self::Immediate => 1,
Self::Standard => 6,
Self::Economy => 144,
}
}
pub fn from_optional_str(s: Option<&str>) -> Self {
let Some(value) = s else {
return Self::default();
};
if value.eq_ignore_ascii_case("immediate") {
Self::Immediate
} else if value.eq_ignore_ascii_case("standard") {
Self::Standard
} else if value.eq_ignore_ascii_case("economy") {
Self::Economy
} else {
Self::default()
}
}
}
pub fn validate_fee_options(fee_options: &[PaymentTier]) -> Result<(), String> {
if fee_options.is_empty() {
return Err("BDK batch_config.fee_options must not be empty".to_string());
}
if fee_options.len() > 3 {
return Err("BDK batch_config.fee_options must contain at most 3 entries".to_string());
}
for (idx, tier) in fee_options.iter().enumerate() {
if fee_options[..idx].contains(tier) {
return Err(format!(
"BDK batch_config.fee_options contains duplicate tier '{}'",
tier.config_name()
));
}
}
Ok(())
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct PaymentMetadata {
pub entries: std::collections::HashMap<String, String>,
}
impl PaymentMetadata {
pub fn from_optional_json(json: Option<&str>) -> Self {
let Some(s) = json else {
return Self::default();
};
if let Ok(meta) = serde_json::from_str::<PaymentMetadata>(s) {
return meta;
}
if let Ok(entries) = serde_json::from_str::<std::collections::HashMap<String, String>>(s) {
return Self { entries };
}
Self::default()
}
}