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