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_LNV2_ENV: &str = "FM_ENABLE_MODULE_LNV2";
20
21pub const FM_DISABLE_BASE_FEES_ENV: &str = "FM_DISABLE_BASE_FEES";
23
24pub const FM_DEBUG_SHOW_SECRETS_ENV: &str = "FM_DEBUG_SHOW_SECRETS";
26
27pub fn is_env_var_set(var: &str) -> bool {
30 let Some(val) = std::env::var_os(var) else {
31 return false;
32 };
33 match val.as_encoded_bytes() {
34 b"0" | b"false" => false,
35 b"1" | b"true" => true,
36 _ => {
37 warn!(
38 target: LOG_CORE,
39 %var,
40 val = %val.to_string_lossy(),
41 "Env var value invalid is invalid and ignored, assuming `true`"
42 );
43 true
44 }
45 }
46}
47
48pub fn is_env_var_set_opt(var: &str) -> Option<bool> {
52 let val = std::env::var_os(var)?;
53 match val.as_encoded_bytes() {
54 b"0" | b"false" => Some(false),
55 b"1" | b"true" => Some(true),
56 _ => {
57 warn!(
58 target: LOG_CORE,
59 %var,
60 val = %val.to_string_lossy(),
61 "Env var value invalid is invalid and ignored"
62 );
63 None
64 }
65 }
66}
67
68pub fn is_running_in_test_env() -> bool {
71 let unit_test = cfg!(test);
72
73 unit_test || is_env_var_set("NEXTEST") || is_env_var_set(FM_IN_DEVIMINT_ENV)
74}
75
76pub fn is_rbf_withdrawal_enabled() -> bool {
78 is_env_var_set("FM_UNSAFE_ENABLE_RBF_WITHDRAWAL")
79}
80
81#[macro_export]
83macro_rules! fedimint_build_code_version_env {
84 () => {
85 env!("FEDIMINT_BUILD_CODE_VERSION")
86 };
87}
88
89pub const FM_BITCOIN_RPC_KIND_ENV: &str = "FM_BITCOIN_RPC_KIND";
91pub const FM_BITCOIN_RPC_URL_ENV: &str = "FM_BITCOIN_RPC_URL";
93pub const FM_BITCOIN_POLLING_INTERVAL_SECS_ENV: &str = "FM_BITCOIN_POLLING_INTERVAL_SECS";
95
96pub const FM_DEFAULT_BITCOIN_RPC_KIND_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_KIND";
99pub const FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_KIND";
100pub const FM_DEFAULT_BITCOIN_RPC_URL_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_URL";
103pub const FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_URL";
104
105pub const FM_FORCE_BITCOIN_RPC_KIND_ENV: &str = "FM_FORCE_BITCOIN_RPC_KIND";
107pub const FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_BAD_KIND";
108pub const FM_FORCE_BITCOIN_RPC_URL_ENV: &str = "FM_FORCE_BITCOIN_RPC_URL";
110pub const FM_FORCE_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_URL";
111
112pub const FM_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_IROH_CONNECT_OVERRIDES";
116
117pub const FM_GW_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_GW_IROH_CONNECT_OVERRIDES";
121
122pub const FM_IROH_DNS_ENV: &str = "FM_IROH_DNS";
124
125pub const FM_IROH_RELAY_ENV: &str = "FM_IROH_RELAY";
127
128pub const FM_IROH_DHT_ENABLE_ENV: &str = "FM_IROH_DHT_ENABLE";
130
131pub const FM_IROH_N0_DISCOVERY_ENABLE_ENV: &str = "FM_IROH_N0_DISCOVERY_ENABLE";
133
134pub const FM_IROH_PKARR_RESOLVER_ENABLE_ENV: &str = "FM_IROH_PKARR_RESOLVER_ENABLE";
136
137pub const FM_IROH_PKARR_PUBLISHER_ENABLE_ENV: &str = "FM_IROH_PKARR_PUBLISHER_ENABLE";
139
140pub const FM_IROH_RELAYS_ENABLE_ENV: &str = "FM_IROH_RELAYS_ENABLE";
142
143pub const FM_WS_API_CONNECT_OVERRIDES_ENV: &str = "FM_WS_API_CONNECT_OVERRIDES";
147
148pub const FM_IROH_API_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_API_SECRET_KEY_OVERRIDE";
149pub const FM_IROH_P2P_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_P2P_SECRET_KEY_OVERRIDE";
150
151pub const FM_WALLET_FEERATE_SOURCES_ENV: &str = "FM_WALLET_FEERATE_SOURCES";
161
162pub const FM_IN_DEVIMINT_ENV: &str = "FM_IN_DEVIMINT";
164
165#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
167pub struct BitcoinRpcConfig {
168 pub kind: String,
169 pub url: SafeUrl,
170}
171
172impl BitcoinRpcConfig {
173 pub fn get_defaults_from_env_vars() -> anyhow::Result<Self> {
174 Ok(Self {
175 kind: env::var(FM_FORCE_BITCOIN_RPC_KIND_ENV)
176 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_ENV))
177 .or_else(|_| env::var(FM_BITCOIN_RPC_KIND_ENV).inspect(|_v| {
178 warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_KIND_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
179 }))
180 .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
181 warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_KIND_ENV} instead");
182 }))
183 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
184 warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
185 }))
186 .with_context(|| {
187 anyhow::anyhow!("failure looking up env var for Bitcoin RPC kind")
188 })?,
189 url: env::var(FM_FORCE_BITCOIN_RPC_URL_ENV)
190 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_ENV))
191 .or_else(|_| env::var(FM_BITCOIN_RPC_URL_ENV).inspect(|_v| {
192 warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_URL_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
193 }))
194 .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
195 warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_URL_ENV} instead");
196 }))
197 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
198 warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
199 }))
200 .with_context(|| {
201 anyhow::anyhow!("failure looking up env var for Bitcoin RPC URL")
202 })?
203 .parse()
204 .with_context(|| {
205 anyhow::anyhow!("failure parsing Bitcoin RPC URL")
206 })?,
207 })
208 }
209}
210
211pub fn parse_kv_list_from_env<K, V>(env: &str) -> anyhow::Result<BTreeMap<K, V>>
212where
213 K: FromStr + cmp::Ord,
214 <K as FromStr>::Err: std::error::Error,
215 V: FromStr,
216 <V as FromStr>::Err: std::error::Error,
217{
218 let mut map = BTreeMap::new();
219 let Ok(env_value) = std::env::var(env) else {
220 return Ok(BTreeMap::new());
221 };
222 for kv in env_value.split(',') {
223 let kv = kv.trim();
224
225 if kv.is_empty() {
226 continue;
227 }
228
229 if let Some((k, v)) = kv.split_once('=') {
230 let Some(k) = K::from_str(k)
231 .inspect_err(|err| {
232 warn!(
233 target: LOG_CORE,
234 err = %err.fmt_compact(),
235 "Error parsing value"
236 );
237 })
238 .ok()
239 else {
240 continue;
241 };
242 let Some(v) = V::from_str(v)
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
255 map.insert(k, v);
256 }
257 }
258
259 Ok(map)
260}