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_WALLET_ENV: &str = "FM_ENABLE_MODULE_WALLET";
22pub const FM_ENABLE_MODULE_WALLETV2_ENV: &str = "FM_ENABLE_MODULE_WALLETV2";
23
24pub const FM_DISABLE_BASE_FEES_ENV: &str = "FM_DISABLE_BASE_FEES";
26
27pub const FM_DEBUG_SHOW_SECRETS_ENV: &str = "FM_DEBUG_SHOW_SECRETS";
29
30pub fn is_env_var_set(var: &str) -> bool {
33 let Some(val) = std::env::var_os(var) else {
34 return false;
35 };
36 match val.as_encoded_bytes() {
37 b"0" | b"false" => false,
38 b"1" | b"true" => true,
39 _ => {
40 warn!(
41 target: LOG_CORE,
42 %var,
43 val = %val.to_string_lossy(),
44 "Env var value invalid is invalid and ignored, assuming `true`"
45 );
46 true
47 }
48 }
49}
50
51pub fn is_env_var_set_opt(var: &str) -> Option<bool> {
55 let val = std::env::var_os(var)?;
56 match val.as_encoded_bytes() {
57 b"0" | b"false" => Some(false),
58 b"1" | b"true" => Some(true),
59 _ => {
60 warn!(
61 target: LOG_CORE,
62 %var,
63 val = %val.to_string_lossy(),
64 "Env var value invalid is invalid and ignored"
65 );
66 None
67 }
68 }
69}
70
71pub fn is_running_in_test_env() -> bool {
74 let unit_test = cfg!(test);
75
76 unit_test || is_env_var_set("NEXTEST") || is_env_var_set(FM_IN_DEVIMINT_ENV)
77}
78
79pub fn is_rbf_withdrawal_enabled() -> bool {
81 is_env_var_set("FM_UNSAFE_ENABLE_RBF_WITHDRAWAL")
82}
83
84#[macro_export]
86macro_rules! fedimint_build_code_version_env {
87 () => {
88 env!("FEDIMINT_BUILD_CODE_VERSION")
89 };
90}
91
92pub const FM_BITCOIN_RPC_KIND_ENV: &str = "FM_BITCOIN_RPC_KIND";
94pub const FM_BITCOIN_RPC_URL_ENV: &str = "FM_BITCOIN_RPC_URL";
96pub const FM_BITCOIN_POLLING_INTERVAL_SECS_ENV: &str = "FM_BITCOIN_POLLING_INTERVAL_SECS";
98
99pub const FM_DEFAULT_BITCOIN_RPC_KIND_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_KIND";
102pub const FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_KIND";
103pub const FM_DEFAULT_BITCOIN_RPC_URL_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_URL";
106pub const FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_URL";
107
108pub const FM_FORCE_BITCOIN_RPC_KIND_ENV: &str = "FM_FORCE_BITCOIN_RPC_KIND";
110pub const FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_BAD_KIND";
111pub const FM_FORCE_BITCOIN_RPC_URL_ENV: &str = "FM_FORCE_BITCOIN_RPC_URL";
113pub const FM_FORCE_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_URL";
114
115pub const FM_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_IROH_CONNECT_OVERRIDES";
119
120pub const FM_GW_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_GW_IROH_CONNECT_OVERRIDES";
124
125pub const FM_IROH_DNS_ENV: &str = "FM_IROH_DNS";
127
128pub const FM_IROH_RELAY_ENV: &str = "FM_IROH_RELAY";
130
131pub const FM_IROH_DHT_ENABLE_ENV: &str = "FM_IROH_DHT_ENABLE";
133
134pub const FM_IROH_N0_DISCOVERY_ENABLE_ENV: &str = "FM_IROH_N0_DISCOVERY_ENABLE";
136
137pub const FM_IROH_PKARR_RESOLVER_ENABLE_ENV: &str = "FM_IROH_PKARR_RESOLVER_ENABLE";
139
140pub const FM_IROH_PKARR_PUBLISHER_ENABLE_ENV: &str = "FM_IROH_PKARR_PUBLISHER_ENABLE";
142
143pub const FM_IROH_RELAYS_ENABLE_ENV: &str = "FM_IROH_RELAYS_ENABLE";
145
146pub const FM_PKARR_ENABLE_ENV: &str = "FM_PKARR_ENABLE";
148
149pub const FM_PKARR_DHT_ENABLE_ENV: &str = "FM_PKARR_DHT_ENABLE";
151
152pub const FM_PKARR_RELAYS_ENABLE_ENV: &str = "FM_PKARR_RELAYS_ENABLE";
154
155pub const FM_WS_API_CONNECT_OVERRIDES_ENV: &str = "FM_WS_API_CONNECT_OVERRIDES";
159
160pub const FM_IROH_API_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_API_SECRET_KEY_OVERRIDE";
161pub const FM_IROH_P2P_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_P2P_SECRET_KEY_OVERRIDE";
162
163pub const FM_WALLET_FEERATE_SOURCES_ENV: &str = "FM_WALLET_FEERATE_SOURCES";
173
174pub const FM_IN_DEVIMINT_ENV: &str = "FM_IN_DEVIMINT";
176
177#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
179pub struct BitcoinRpcConfig {
180 pub kind: String,
181 pub url: SafeUrl,
182}
183
184impl BitcoinRpcConfig {
185 pub fn get_defaults_from_env_vars() -> anyhow::Result<Self> {
186 Ok(Self {
187 kind: env::var(FM_FORCE_BITCOIN_RPC_KIND_ENV)
188 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_ENV))
189 .or_else(|_| env::var(FM_BITCOIN_RPC_KIND_ENV).inspect(|_v| {
190 warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_KIND_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
191 }))
192 .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
193 warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_KIND_ENV} instead");
194 }))
195 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
196 warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
197 }))
198 .with_context(|| {
199 anyhow::anyhow!("failure looking up env var for Bitcoin RPC kind")
200 })?,
201 url: env::var(FM_FORCE_BITCOIN_RPC_URL_ENV)
202 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_ENV))
203 .or_else(|_| env::var(FM_BITCOIN_RPC_URL_ENV).inspect(|_v| {
204 warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_URL_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
205 }))
206 .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
207 warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_URL_ENV} instead");
208 }))
209 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
210 warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
211 }))
212 .with_context(|| {
213 anyhow::anyhow!("failure looking up env var for Bitcoin RPC URL")
214 })?
215 .parse()
216 .with_context(|| {
217 anyhow::anyhow!("failure parsing Bitcoin RPC URL")
218 })?,
219 })
220 }
221}
222
223pub fn parse_kv_list_from_env<K, V>(env: &str) -> anyhow::Result<BTreeMap<K, V>>
224where
225 K: FromStr + cmp::Ord,
226 <K as FromStr>::Err: std::error::Error,
227 V: FromStr,
228 <V as FromStr>::Err: std::error::Error,
229{
230 let mut map = BTreeMap::new();
231 let Ok(env_value) = std::env::var(env) else {
232 return Ok(BTreeMap::new());
233 };
234 for kv in env_value.split(',') {
235 let kv = kv.trim();
236
237 if kv.is_empty() {
238 continue;
239 }
240
241 if let Some((k, v)) = kv.split_once('=') {
242 let Some(k) = K::from_str(k)
243 .inspect_err(|err| {
244 warn!(
245 target: LOG_CORE,
246 err = %err.fmt_compact(),
247 "Error parsing value"
248 );
249 })
250 .ok()
251 else {
252 continue;
253 };
254 let Some(v) = V::from_str(v)
255 .inspect_err(|err| {
256 warn!(
257 target: LOG_CORE,
258 err = %err.fmt_compact(),
259 "Error parsing value"
260 );
261 })
262 .ok()
263 else {
264 continue;
265 };
266
267 map.insert(k, v);
268 }
269 }
270
271 Ok(map)
272}