1use std::collections::BTreeMap;
2use std::str::FromStr;
3use std::{cmp, env};
4
5use anyhow::Context;
6use fedimint_core::util::SafeUrl;
7use fedimint_derive::{Decodable, Encodable};
8use fedimint_logging::LOG_CORE;
9use jsonrpsee_core::Serialize;
10use serde::Deserialize;
11use tracing::warn;
12
13use crate::util::FmtCompact as _;
14
15pub const FM_USE_UNKNOWN_MODULE_ENV: &str = "FM_USE_UNKNOWN_MODULE";
18
19pub const FM_ENABLE_MODULE_LNV1_ENV: &str = "FM_ENABLE_MODULE_LNV1";
20pub const FM_ENABLE_MODULE_LNV2_ENV: &str = "FM_ENABLE_MODULE_LNV2";
21pub const FM_ENABLE_MODULE_MINT_ENV: &str = "FM_ENABLE_MODULE_MINT";
22pub const FM_ENABLE_MODULE_MINTV2_ENV: &str = "FM_ENABLE_MODULE_MINTV2";
23pub const FM_ENABLE_MODULE_WALLET_ENV: &str = "FM_ENABLE_MODULE_WALLET";
24pub const FM_ENABLE_MODULE_WALLETV2_ENV: &str = "FM_ENABLE_MODULE_WALLETV2";
25
26pub const FM_DISABLE_BASE_FEES_ENV: &str = "FM_DISABLE_BASE_FEES";
28
29pub const FM_DEBUG_SHOW_SECRETS_ENV: &str = "FM_DEBUG_SHOW_SECRETS";
31
32pub fn is_env_var_set(var: &str) -> bool {
35 let Some(val) = std::env::var_os(var) else {
36 return false;
37 };
38 match val.as_encoded_bytes() {
39 b"0" | b"false" => false,
40 b"1" | b"true" => true,
41 _ => {
42 warn!(
43 target: LOG_CORE,
44 %var,
45 val = %val.to_string_lossy(),
46 "Env var value invalid is invalid and ignored, assuming `true`"
47 );
48 true
49 }
50 }
51}
52
53pub fn is_env_var_set_opt(var: &str) -> Option<bool> {
57 let val = std::env::var_os(var)?;
58 match val.as_encoded_bytes() {
59 b"0" | b"false" => Some(false),
60 b"1" | b"true" => Some(true),
61 _ => {
62 warn!(
63 target: LOG_CORE,
64 %var,
65 val = %val.to_string_lossy(),
66 "Env var value invalid is invalid and ignored"
67 );
68 None
69 }
70 }
71}
72
73pub fn is_running_in_test_env() -> bool {
76 let unit_test = cfg!(test);
77
78 unit_test || is_env_var_set("NEXTEST") || is_env_var_set(FM_IN_DEVIMINT_ENV)
79}
80
81pub fn is_rbf_withdrawal_enabled() -> bool {
83 is_env_var_set("FM_UNSAFE_ENABLE_RBF_WITHDRAWAL")
84}
85
86#[macro_export]
88macro_rules! fedimint_build_code_version_env {
89 () => {
90 env!("FEDIMINT_BUILD_CODE_VERSION")
91 };
92}
93
94pub const FM_BITCOIN_RPC_KIND_ENV: &str = "FM_BITCOIN_RPC_KIND";
96pub const FM_BITCOIN_RPC_URL_ENV: &str = "FM_BITCOIN_RPC_URL";
98pub const FM_BITCOIN_POLLING_INTERVAL_SECS_ENV: &str = "FM_BITCOIN_POLLING_INTERVAL_SECS";
100
101pub const FM_DEFAULT_BITCOIN_RPC_KIND_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_KIND";
104pub const FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_KIND";
105pub const FM_DEFAULT_BITCOIN_RPC_URL_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_URL";
108pub const FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_URL";
109
110pub const FM_FORCE_BITCOIN_RPC_KIND_ENV: &str = "FM_FORCE_BITCOIN_RPC_KIND";
112pub const FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_BAD_KIND";
113pub const FM_FORCE_BITCOIN_RPC_URL_ENV: &str = "FM_FORCE_BITCOIN_RPC_URL";
115pub const FM_FORCE_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_URL";
116
117pub const FM_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_IROH_CONNECT_OVERRIDES";
121
122pub const FM_GW_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_GW_IROH_CONNECT_OVERRIDES";
126
127pub const FM_IROH_DNS_ENV: &str = "FM_IROH_DNS";
129
130pub const FM_IROH_RELAY_ENV: &str = "FM_IROH_RELAY";
132
133pub const FM_IROH_DHT_ENABLE_ENV: &str = "FM_IROH_DHT_ENABLE";
135
136pub const FM_IROH_N0_DISCOVERY_ENABLE_ENV: &str = "FM_IROH_N0_DISCOVERY_ENABLE";
138
139pub const FM_IROH_PKARR_RESOLVER_ENABLE_ENV: &str = "FM_IROH_PKARR_RESOLVER_ENABLE";
141
142pub const FM_IROH_PKARR_PUBLISHER_ENABLE_ENV: &str = "FM_IROH_PKARR_PUBLISHER_ENABLE";
144
145pub const FM_IROH_RELAYS_ENABLE_ENV: &str = "FM_IROH_RELAYS_ENABLE";
147
148pub const FM_PKARR_ENABLE_ENV: &str = "FM_PKARR_ENABLE";
150
151pub const FM_PKARR_DHT_ENABLE_ENV: &str = "FM_PKARR_DHT_ENABLE";
153
154pub const FM_PKARR_RELAYS_ENABLE_ENV: &str = "FM_PKARR_RELAYS_ENABLE";
156
157pub const FM_WS_API_CONNECT_OVERRIDES_ENV: &str = "FM_WS_API_CONNECT_OVERRIDES";
161
162pub const FM_IROH_API_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_API_SECRET_KEY_OVERRIDE";
163pub const FM_IROH_P2P_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_P2P_SECRET_KEY_OVERRIDE";
164
165pub const FM_WALLET_FEERATE_SOURCES_ENV: &str = "FM_WALLET_FEERATE_SOURCES";
175
176pub const FM_IN_DEVIMINT_ENV: &str = "FM_IN_DEVIMINT";
178
179#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
181pub struct BitcoinRpcConfig {
182 pub kind: String,
183 pub url: SafeUrl,
184}
185
186impl BitcoinRpcConfig {
187 pub fn get_defaults_from_env_vars() -> anyhow::Result<Self> {
188 Ok(Self {
189 kind: env::var(FM_FORCE_BITCOIN_RPC_KIND_ENV)
190 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_ENV))
191 .or_else(|_| env::var(FM_BITCOIN_RPC_KIND_ENV).inspect(|_v| {
192 warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_KIND_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
193 }))
194 .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
195 warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_KIND_ENV} instead");
196 }))
197 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
198 warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
199 }))
200 .with_context(|| {
201 anyhow::anyhow!("failure looking up env var for Bitcoin RPC kind")
202 })?,
203 url: env::var(FM_FORCE_BITCOIN_RPC_URL_ENV)
204 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_ENV))
205 .or_else(|_| env::var(FM_BITCOIN_RPC_URL_ENV).inspect(|_v| {
206 warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_URL_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
207 }))
208 .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
209 warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_URL_ENV} instead");
210 }))
211 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
212 warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
213 }))
214 .with_context(|| {
215 anyhow::anyhow!("failure looking up env var for Bitcoin RPC URL")
216 })?
217 .parse()
218 .with_context(|| {
219 anyhow::anyhow!("failure parsing Bitcoin RPC URL")
220 })?,
221 })
222 }
223}
224
225pub fn parse_kv_list_from_env<K, V>(env: &str) -> anyhow::Result<BTreeMap<K, V>>
226where
227 K: FromStr + cmp::Ord,
228 <K as FromStr>::Err: std::error::Error,
229 V: FromStr,
230 <V as FromStr>::Err: std::error::Error,
231{
232 let mut map = BTreeMap::new();
233 let Ok(env_value) = std::env::var(env) else {
234 return Ok(BTreeMap::new());
235 };
236 for kv in env_value.split(',') {
237 let kv = kv.trim();
238
239 if kv.is_empty() {
240 continue;
241 }
242
243 if let Some((k, v)) = kv.split_once('=') {
244 let Some(k) = K::from_str(k)
245 .inspect_err(|err| {
246 warn!(
247 target: LOG_CORE,
248 err = %err.fmt_compact(),
249 "Error parsing value"
250 );
251 })
252 .ok()
253 else {
254 continue;
255 };
256 let Some(v) = V::from_str(v)
257 .inspect_err(|err| {
258 warn!(
259 target: LOG_CORE,
260 err = %err.fmt_compact(),
261 "Error parsing value"
262 );
263 })
264 .ok()
265 else {
266 continue;
267 };
268
269 map.insert(k, v);
270 }
271 }
272
273 Ok(map)
274}