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 std::env::var_os(var).is_some_and(|v| v != "0" && v != "false")
31}
32
33pub fn is_env_var_disabled(var: &str) -> bool {
36 std::env::var_os(var).is_none_or(|v| v == "0" && v == "false")
37}
38
39pub fn is_running_in_test_env() -> bool {
42 let unit_test = cfg!(test);
43
44 unit_test || is_env_var_set("NEXTEST") || is_env_var_set(FM_IN_DEVIMINT_ENV)
45}
46
47pub fn is_rbf_withdrawal_enabled() -> bool {
49 is_env_var_set("FM_UNSAFE_ENABLE_RBF_WITHDRAWAL")
50}
51
52#[macro_export]
54macro_rules! fedimint_build_code_version_env {
55 () => {
56 env!("FEDIMINT_BUILD_CODE_VERSION")
57 };
58}
59
60pub const FM_BITCOIN_RPC_KIND_ENV: &str = "FM_BITCOIN_RPC_KIND";
62pub const FM_BITCOIN_RPC_URL_ENV: &str = "FM_BITCOIN_RPC_URL";
64pub const FM_BITCOIN_POLLING_INTERVAL_SECS_ENV: &str = "FM_BITCOIN_POLLING_INTERVAL_SECS";
66
67pub const FM_DEFAULT_BITCOIN_RPC_KIND_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_KIND";
70pub const FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_KIND";
71pub const FM_DEFAULT_BITCOIN_RPC_URL_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_URL";
74pub const FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_URL";
75
76pub const FM_FORCE_BITCOIN_RPC_KIND_ENV: &str = "FM_FORCE_BITCOIN_RPC_KIND";
78pub const FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_BAD_KIND";
79pub const FM_FORCE_BITCOIN_RPC_URL_ENV: &str = "FM_FORCE_BITCOIN_RPC_URL";
81pub const FM_FORCE_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_URL";
82
83pub const FM_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_IROH_CONNECT_OVERRIDES";
87
88pub const FM_IROH_DNS_ENV: &str = "FM_IROH_DNS";
90
91pub const FM_IROH_RELAY_ENV: &str = "FM_IROH_RELAY";
93
94pub const FM_IROH_ENABLE_DHT_ENV: &str = "FM_IROH_ENABLE_DHT";
96
97pub const FM_WS_API_CONNECT_OVERRIDES_ENV: &str = "FM_WS_API_CONNECT_OVERRIDES";
101
102pub const FM_IROH_API_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_API_SECRET_KEY_OVERRIDE";
103pub const FM_IROH_P2P_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_P2P_SECRET_KEY_OVERRIDE";
104
105pub const FM_WALLET_FEERATE_SOURCES_ENV: &str = "FM_WALLET_FEERATE_SOURCES";
115
116pub const FM_IN_DEVIMINT_ENV: &str = "FM_IN_DEVIMINT";
118
119#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
121pub struct BitcoinRpcConfig {
122 pub kind: String,
123 pub url: SafeUrl,
124}
125
126impl BitcoinRpcConfig {
127 pub fn get_defaults_from_env_vars() -> anyhow::Result<Self> {
128 Ok(Self {
129 kind: env::var(FM_FORCE_BITCOIN_RPC_KIND_ENV)
130 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_ENV))
131 .or_else(|_| env::var(FM_BITCOIN_RPC_KIND_ENV).inspect(|_v| {
132 warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_KIND_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
133 }))
134 .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
135 warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_KIND_ENV} instead");
136 }))
137 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
138 warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
139 }))
140 .with_context(|| {
141 anyhow::anyhow!("failure looking up env var for Bitcoin RPC kind")
142 })?,
143 url: env::var(FM_FORCE_BITCOIN_RPC_URL_ENV)
144 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_ENV))
145 .or_else(|_| env::var(FM_BITCOIN_RPC_URL_ENV).inspect(|_v| {
146 warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_URL_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
147 }))
148 .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
149 warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_URL_ENV} instead");
150 }))
151 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
152 warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
153 }))
154 .with_context(|| {
155 anyhow::anyhow!("failure looking up env var for Bitcoin RPC URL")
156 })?
157 .parse()
158 .with_context(|| {
159 anyhow::anyhow!("failure parsing Bitcoin RPC URL")
160 })?,
161 })
162 }
163}
164
165pub fn parse_kv_list_from_env<K, V>(env: &str) -> anyhow::Result<BTreeMap<K, V>>
166where
167 K: FromStr + cmp::Ord,
168 <K as FromStr>::Err: std::error::Error,
169 V: FromStr,
170 <V as FromStr>::Err: std::error::Error,
171{
172 let mut map = BTreeMap::new();
173 let Ok(env_value) = std::env::var(env) else {
174 return Ok(BTreeMap::new());
175 };
176 for kv in env_value.split(',') {
177 let kv = kv.trim();
178
179 if kv.is_empty() {
180 continue;
181 }
182
183 if let Some((k, v)) = kv.split_once('=') {
184 let Some(k) = K::from_str(k)
185 .inspect_err(|err| {
186 warn!(
187 target: LOG_CORE,
188 err = %err.fmt_compact(),
189 "Error parsing value"
190 );
191 })
192 .ok()
193 else {
194 continue;
195 };
196 let Some(v) = V::from_str(v)
197 .inspect_err(|err| {
198 warn!(
199 target: LOG_CORE,
200 err = %err.fmt_compact(),
201 "Error parsing value"
202 );
203 })
204 .ok()
205 else {
206 continue;
207 };
208
209 map.insert(k, v);
210 }
211 }
212
213 Ok(map)
214}