1use crate::MempoolConfig;
4use bitcoin::secp256k1::SecretKey;
5use bitcoin::Network;
6use clementine_config::tx_sender::TxSenderLimits;
7use clementine_errors::BridgeError;
8use secrecy::SecretString;
9use std::str::FromStr;
10
11const DEFAULT_POLL_DELAY_MS: u64 = 30_000;
12
13#[derive(Clone, Debug)]
14pub struct TxSenderPostgresConfig {
15 pub host: String,
16 pub port: u16,
17 pub user: SecretString,
18 pub password: SecretString,
19 pub dbname: String,
20}
21
22#[derive(Clone, Debug)]
23pub struct TxSenderBitcoinRpcConfig {
24 pub url: String,
25 pub user: SecretString,
26 pub password: SecretString,
27}
28
29#[derive(Clone, Debug)]
30pub struct TxSenderJsonRpcConfig {
31 pub bind: String,
33 pub port: u16,
35}
36
37#[derive(Clone, Debug)]
39pub struct TxSenderConfig {
40 pub network: Network,
41 pub secret_key: SecretKey,
46 pub private_da_key: Option<SecretKey>,
50 pub postgres: TxSenderPostgresConfig,
51 pub bitcoin_rpc: TxSenderBitcoinRpcConfig,
52 pub mempool: MempoolConfig,
53 pub limits: TxSenderLimits,
54 pub finality_depth: u32,
58
59 pub poll_delay_ms: u64,
63
64 pub include_unsafe: bool,
66
67 pub jsonrpc: Option<TxSenderJsonRpcConfig>,
69}
70
71fn env_required(name: &'static str) -> Result<String, BridgeError> {
72 std::env::var(name).map_err(|e| BridgeError::EnvVarNotSet(e, name))
73}
74
75fn env_optional(name: &'static str) -> Option<String> {
76 std::env::var(name).ok()
77}
78
79fn env_parse_required<T: std::str::FromStr>(name: &'static str) -> Result<T, BridgeError>
80where
81 <T as std::str::FromStr>::Err: std::fmt::Debug,
82{
83 env_required(name)?
84 .parse::<T>()
85 .map_err(|e| BridgeError::EnvVarMalformed(name, format!("{e:?}")))
86}
87
88fn env_parse_optional<T: std::str::FromStr>(name: &'static str) -> Result<Option<T>, BridgeError>
89where
90 <T as std::str::FromStr>::Err: std::fmt::Debug,
91{
92 let Some(v) = env_optional(name) else {
93 return Ok(None);
94 };
95 v.parse::<T>()
96 .map(Some)
97 .map_err(|e| BridgeError::EnvVarMalformed(name, format!("{e:?}")))
98}
99
100fn env_parse_optional_or<T: std::str::FromStr>(
101 name: &'static str,
102 default: T,
103) -> Result<T, BridgeError>
104where
105 <T as std::str::FromStr>::Err: std::fmt::Debug,
106{
107 Ok(env_parse_optional::<T>(name)?.unwrap_or(default))
108}
109
110impl TxSenderConfig {
111 pub fn from_env() -> Result<Self, BridgeError> {
112 let network_str = env_required("NETWORK")?;
113 let network = Network::from_str(&network_str)
114 .map_err(|e| BridgeError::EnvVarMalformed("NETWORK", format!("{e:?}")))?;
115
116 let secret_key_str = env_required("SECRET_KEY")?;
117 let secret_key = SecretKey::from_str(&secret_key_str)
118 .map_err(|e| BridgeError::EnvVarMalformed("SECRET_KEY", format!("{e:?}")))?;
119
120 let private_da_key =
121 match env_optional("PRIVATE_DA_KEY") {
122 Some(value) => Some(SecretKey::from_str(&value).map_err(|e| {
123 BridgeError::EnvVarMalformed("PRIVATE_DA_KEY", format!("{e:?}"))
124 })?),
125 None => None,
126 };
127
128 let postgres = TxSenderPostgresConfig {
129 host: env_required("DB_HOST")?,
130 port: env_parse_required::<u16>("DB_PORT")?,
131 user: env_required("DB_USER")?.into(),
132 password: env_required("DB_PASSWORD")?.into(),
133 dbname: env_required("DB_NAME")?,
134 };
135
136 let bitcoin_rpc = TxSenderBitcoinRpcConfig {
137 url: env_required("BITCOIN_RPC_URL")?,
138 user: env_required("BITCOIN_RPC_USER")?.into(),
139 password: env_required("BITCOIN_RPC_PASSWORD")?.into(),
140 };
141
142 let mempool = MempoolConfig {
143 host: env_optional("MEMPOOL_API_HOST"),
144 endpoint: env_optional("MEMPOOL_API_ENDPOINT"),
145 };
146
147 let defaults = TxSenderLimits::default();
150 let limits = TxSenderLimits {
151 fee_rate_hard_cap: env_parse_optional_or::<u64>(
152 "TX_SENDER_FEE_RATE_HARD_CAP",
153 defaults.fee_rate_hard_cap,
154 )?,
155 mempool_fee_rate_multiplier: env_parse_optional_or::<u64>(
156 "TX_SENDER_MEMPOOL_FEE_RATE_MULTIPLIER",
157 defaults.mempool_fee_rate_multiplier,
158 )?,
159 mempool_fee_rate_offset_sat_kvb: env_parse_optional_or::<u64>(
160 "TX_SENDER_MEMPOOL_FEE_RATE_OFFSET_SAT_KVB",
161 defaults.mempool_fee_rate_offset_sat_kvb,
162 )?,
163 cpfp_fee_payer_bump_wait_time_seconds: env_parse_optional_or::<u64>(
164 "TX_SENDER_CPFP_FEE_PAYER_BUMP_WAIT_TIME_SECONDS",
165 defaults.cpfp_fee_payer_bump_wait_time_seconds,
166 )?,
167 fee_bump_after_blocks: env_parse_optional_or::<u32>(
168 "TX_SENDER_FEE_BUMP_AFTER_BLOCKS",
169 defaults.fee_bump_after_blocks,
170 )?,
171 min_bump_kvb: env_parse_optional_or::<u64>(
172 "TX_SENDER_MIN_BUMP_KVB",
173 defaults.min_bump_kvb,
174 )?,
175 };
176
177 let finality_depth = env_parse_required::<u32>("TX_SENDER_FINALITY_DEPTH")?;
178
179 let poll_delay_ms =
180 env_parse_optional::<u64>("TX_SENDER_POLL_DELAY_MS")?.unwrap_or(DEFAULT_POLL_DELAY_MS);
181 if poll_delay_ms == 0 {
182 return Err(BridgeError::EnvVarMalformed(
183 "TX_SENDER_POLL_DELAY_MS",
184 "poll_delay_ms must be >= 1".to_string(),
185 ));
186 }
187
188 let include_unsafe = env_parse_required::<bool>("TX_SENDER_INCLUDE_UNSAFE")?;
189
190 if finality_depth < 1 {
191 return Err(BridgeError::EnvVarMalformed(
192 "TX_SENDER_FINALITY_DEPTH",
193 "finality depth must be >= 1".to_string(),
194 ));
195 }
196
197 #[cfg(feature = "json-rpc")]
198 let jsonrpc = {
199 let port = env_parse_optional::<u16>("TX_SENDER_JSONRPC_PORT")?;
200 port.map(|port| {
201 let bind = env_optional("TX_SENDER_JSONRPC_BIND")
202 .unwrap_or_else(|| "127.0.0.1".to_string());
203 if bind != "127.0.0.1" && bind != "0.0.0.0" {
204 return Err(BridgeError::EnvVarMalformed(
205 "TX_SENDER_JSONRPC_BIND",
206 "bind must be either 127.0.0.1 or 0.0.0.0".to_string(),
207 ));
208 }
209 Ok(TxSenderJsonRpcConfig { bind, port })
210 })
211 .transpose()?
212 };
213
214 #[cfg(not(feature = "json-rpc"))]
215 let jsonrpc = None;
216
217 Ok(Self {
218 network,
219 secret_key,
220 private_da_key,
221 postgres,
222 bitcoin_rpc,
223 mempool,
224 limits,
225 finality_depth,
226 poll_delay_ms,
227 include_unsafe,
228 jsonrpc,
229 })
230 }
231}