use std::collections::BTreeMap;
use std::str::FromStr;
use std::{cmp, env};
use anyhow::Context;
use fedimint_core::util::SafeUrl;
use fedimint_derive::{Decodable, Encodable};
use fedimint_logging::LOG_CORE;
use jsonrpsee_core::Serialize;
use serde::Deserialize;
use tracing::warn;
use crate::util::FmtCompact as _;
pub const FM_USE_UNKNOWN_MODULE_ENV: &str = "FM_USE_UNKNOWN_MODULE";
pub const FM_ENABLE_MODULE_LNV2_ENV: &str = "FM_ENABLE_MODULE_LNV2";
pub const FM_DISABLE_BASE_FEES_ENV: &str = "FM_DISABLE_BASE_FEES";
pub const FM_DEBUG_SHOW_SECRETS_ENV: &str = "FM_DEBUG_SHOW_SECRETS";
pub fn is_env_var_set(var: &str) -> bool {
std::env::var_os(var).is_some_and(|v| v != "0" && v != "false")
}
pub fn is_env_var_disabled(var: &str) -> bool {
std::env::var_os(var).is_none_or(|v| v == "0" && v == "false")
}
pub fn is_running_in_test_env() -> bool {
let unit_test = cfg!(test);
unit_test || is_env_var_set("NEXTEST") || is_env_var_set(FM_IN_DEVIMINT_ENV)
}
pub fn is_rbf_withdrawal_enabled() -> bool {
is_env_var_set("FM_UNSAFE_ENABLE_RBF_WITHDRAWAL")
}
#[macro_export]
macro_rules! fedimint_build_code_version_env {
() => {
env!("FEDIMINT_BUILD_CODE_VERSION")
};
}
pub const FM_BITCOIN_RPC_KIND_ENV: &str = "FM_BITCOIN_RPC_KIND";
pub const FM_BITCOIN_RPC_URL_ENV: &str = "FM_BITCOIN_RPC_URL";
pub const FM_BITCOIN_POLLING_INTERVAL_SECS_ENV: &str = "FM_BITCOIN_POLLING_INTERVAL_SECS";
pub const FM_DEFAULT_BITCOIN_RPC_KIND_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_KIND";
pub const FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_KIND";
pub const FM_DEFAULT_BITCOIN_RPC_URL_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_URL";
pub const FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_URL";
pub const FM_FORCE_BITCOIN_RPC_KIND_ENV: &str = "FM_FORCE_BITCOIN_RPC_KIND";
pub const FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_BAD_KIND";
pub const FM_FORCE_BITCOIN_RPC_URL_ENV: &str = "FM_FORCE_BITCOIN_RPC_URL";
pub const FM_FORCE_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_URL";
pub const FM_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_IROH_CONNECT_OVERRIDES";
pub const FM_IROH_DNS_ENV: &str = "FM_IROH_DNS";
pub const FM_IROH_RELAY_ENV: &str = "FM_IROH_RELAY";
pub const FM_IROH_ENABLE_DHT_ENV: &str = "FM_IROH_ENABLE_DHT";
pub const FM_WS_API_CONNECT_OVERRIDES_ENV: &str = "FM_WS_API_CONNECT_OVERRIDES";
pub const FM_IROH_API_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_API_SECRET_KEY_OVERRIDE";
pub const FM_IROH_P2P_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_P2P_SECRET_KEY_OVERRIDE";
pub const FM_WALLET_FEERATE_SOURCES_ENV: &str = "FM_WALLET_FEERATE_SOURCES";
pub const FM_IN_DEVIMINT_ENV: &str = "FM_IN_DEVIMINT";
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
pub struct BitcoinRpcConfig {
pub kind: String,
pub url: SafeUrl,
}
impl BitcoinRpcConfig {
pub fn get_defaults_from_env_vars() -> anyhow::Result<Self> {
Ok(Self {
kind: env::var(FM_FORCE_BITCOIN_RPC_KIND_ENV)
.or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_ENV))
.or_else(|_| env::var(FM_BITCOIN_RPC_KIND_ENV).inspect(|_v| {
warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_KIND_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
}))
.or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_KIND_ENV} instead");
}))
.or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
}))
.with_context(|| {
anyhow::anyhow!("failure looking up env var for Bitcoin RPC kind")
})?,
url: env::var(FM_FORCE_BITCOIN_RPC_URL_ENV)
.or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_ENV))
.or_else(|_| env::var(FM_BITCOIN_RPC_URL_ENV).inspect(|_v| {
warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_URL_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
}))
.or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_URL_ENV} instead");
}))
.or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
}))
.with_context(|| {
anyhow::anyhow!("failure looking up env var for Bitcoin RPC URL")
})?
.parse()
.with_context(|| {
anyhow::anyhow!("failure parsing Bitcoin RPC URL")
})?,
})
}
}
pub fn parse_kv_list_from_env<K, V>(env: &str) -> anyhow::Result<BTreeMap<K, V>>
where
K: FromStr + cmp::Ord,
<K as FromStr>::Err: std::error::Error,
V: FromStr,
<V as FromStr>::Err: std::error::Error,
{
let mut map = BTreeMap::new();
let Ok(env_value) = std::env::var(env) else {
return Ok(BTreeMap::new());
};
for kv in env_value.split(',') {
let kv = kv.trim();
if kv.is_empty() {
continue;
}
if let Some((k, v)) = kv.split_once('=') {
let Some(k) = K::from_str(k)
.inspect_err(|err| {
warn!(
target: LOG_CORE,
err = %err.fmt_compact(),
"Error parsing value"
);
})
.ok()
else {
continue;
};
let Some(v) = V::from_str(v)
.inspect_err(|err| {
warn!(
target: LOG_CORE,
err = %err.fmt_compact(),
"Error parsing value"
);
})
.ok()
else {
continue;
};
map.insert(k, v);
}
}
Ok(map)
}