1use std::net::SocketAddr;
2use std::path::PathBuf;
3use std::str::FromStr;
4
5use bitcoin::Network;
6use clap::builder::BoolishValueParser;
7use clap::{ArgGroup, Parser};
8use fedimint_core::envs::{FM_IROH_DNS_ENV, FM_IROH_RELAY_ENV};
9use fedimint_core::util::SafeUrl;
10use fedimint_gateway_common::{LightningMode, V1_API_ENDPOINT};
11use fedimint_lnv2_common::gateway_api::PaymentFee;
12
13use super::envs;
14use crate::envs::{
15 FM_BITCOIND_PASSWORD_ENV, FM_BITCOIND_URL_ENV, FM_BITCOIND_USERNAME_ENV, FM_ESPLORA_URL_ENV,
16 FM_GATEWAY_METRICS_LISTEN_ADDR_ENV, FM_GATEWAY_SKIP_SETUP_ENV,
17};
18
19#[derive(Debug, Clone, Copy, clap::ValueEnum)]
20pub enum DatabaseBackend {
21 #[value(name = "rocksdb")]
23 RocksDb,
24 #[value(name = "cursed-redb")]
26 CursedRedb,
27}
28
29#[derive(Parser)]
32#[command(version)]
33#[command(
34 group(
35 ArgGroup::new("bitcoind_password_auth")
36 .args(["bitcoind_password"])
37 .multiple(false)
38 ),
39 group(
40 ArgGroup::new("bitcoind_auth")
41 .args(["bitcoind_url"])
42 .requires("bitcoind_password_auth")
43 .requires_all(["bitcoind_username", "bitcoind_url"])
44 ),
45 group(
46 ArgGroup::new("bitcoin_rpc")
47 .required(true)
48 .multiple(true)
49 .args(["bitcoind_url", "esplora_url"])
50 )
51)]
52pub struct GatewayOpts {
53 #[clap(subcommand)]
54 pub mode: LightningMode,
55
56 #[arg(long = "data-dir", env = envs::FM_GATEWAY_DATA_DIR_ENV)]
58 pub data_dir: PathBuf,
59
60 #[arg(long = "listen", env = envs::FM_GATEWAY_LISTEN_ADDR_ENV)]
62 listen: SocketAddr,
63
64 #[arg(long = "api-addr", env = envs::FM_GATEWAY_API_ADDR_ENV)]
66 api_addr: Option<SafeUrl>,
67
68 #[arg(long = "bcrypt-password-hash", env = envs::FM_GATEWAY_BCRYPT_PASSWORD_HASH_ENV)]
70 bcrypt_password_hash: String,
71
72 #[arg(long = "bcrypt_liquidity_manager_password_hash", env = envs::FM_GATEWAY_LIQUIDITY_MANAGER_BCRYPT_PASSWORD_HASH_ENV)]
75 bcrypt_liquidity_manager_password_hash: Option<String>,
76
77 #[arg(long = "network", env = envs::FM_GATEWAY_NETWORK_ENV)]
79 network: Network,
80
81 #[arg(
83 long = "num-route-hints",
84 env = envs::FM_NUMBER_OF_ROUTE_HINTS_ENV,
85 default_value_t = super::DEFAULT_NUM_ROUTE_HINTS
86 )]
87 num_route_hints: u32,
88
89 #[arg(long, env = envs::FM_DB_BACKEND_ENV, value_enum, default_value = "rocksdb")]
91 pub db_backend: DatabaseBackend,
92
93 #[arg(long, env = FM_BITCOIND_USERNAME_ENV)]
95 pub bitcoind_username: Option<String>,
96
97 #[arg(long, env = FM_BITCOIND_PASSWORD_ENV)]
99 pub bitcoind_password: Option<String>,
100
101 #[arg(long, env = FM_BITCOIND_URL_ENV)]
105 pub bitcoind_url: Option<SafeUrl>,
106
107 #[arg(long, env = FM_ESPLORA_URL_ENV)]
109 pub esplora_url: Option<SafeUrl>,
110
111 #[arg(long = "default-routing-fees", env = envs::FM_DEFAULT_ROUTING_FEES_ENV, default_value_t = PaymentFee::TRANSACTION_FEE_DEFAULT)]
113 default_routing_fees: PaymentFee,
114
115 #[arg(long = "default-transaction-fees", env = envs::FM_DEFAULT_TRANSACTION_FEES_ENV, default_value_t = PaymentFee::TRANSACTION_FEE_DEFAULT)]
117 default_transaction_fees: PaymentFee,
118
119 #[arg(long = "iroh-listen", env = envs::FM_GATEWAY_IROH_LISTEN_ADDR_ENV)]
121 iroh_listen: Option<SocketAddr>,
122
123 #[arg(long = "metrics-listen", env = FM_GATEWAY_METRICS_LISTEN_ADDR_ENV)]
126 metrics_listen: Option<SocketAddr>,
127
128 #[arg(long, env = FM_IROH_DNS_ENV)]
130 iroh_dns: Option<SafeUrl>,
131
132 #[arg(long, env = FM_IROH_RELAY_ENV, value_delimiter = ',')]
134 iroh_relays: Vec<SafeUrl>,
135
136 #[arg(
137 long,
138 env = FM_GATEWAY_SKIP_SETUP_ENV,
139 default_value_t = false,
140 value_parser = BoolishValueParser::new()
141 )]
142 skip_setup: bool,
143}
144
145impl GatewayOpts {
146 pub fn to_gateway_parameters(&self) -> anyhow::Result<GatewayParameters> {
149 let versioned_api = self.api_addr.clone().map(|api_addr| {
150 api_addr
151 .join(V1_API_ENDPOINT)
152 .expect("Could not join v1 api_addr")
153 });
154 let bcrypt_password_hash = bcrypt::HashParts::from_str(&self.bcrypt_password_hash)?;
155 let bcrypt_liquidity_manager_password_hash =
156 if let Some(h) = &self.bcrypt_liquidity_manager_password_hash {
157 Some(bcrypt::HashParts::from_str(h)?)
158 } else {
159 None
160 };
161
162 let metrics_listen = self.metrics_listen.unwrap_or_else(|| {
164 SocketAddr::new(
165 std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST),
166 self.listen.port() + 1,
167 )
168 });
169
170 Ok(GatewayParameters {
171 listen: self.listen,
172 versioned_api,
173 bcrypt_password_hash,
174 bcrypt_liquidity_manager_password_hash,
175 network: self.network,
176 num_route_hints: self.num_route_hints,
177 default_routing_fees: self.default_routing_fees,
178 default_transaction_fees: self.default_transaction_fees,
179 iroh_listen: self.iroh_listen,
180 iroh_dns: self.iroh_dns.clone(),
181 iroh_relays: self.iroh_relays.clone(),
182 skip_setup: self.skip_setup,
183 metrics_listen,
184 })
185 }
186}
187
188#[derive(Debug)]
195pub struct GatewayParameters {
196 pub listen: SocketAddr,
197 pub versioned_api: Option<SafeUrl>,
198 pub bcrypt_password_hash: bcrypt::HashParts,
199 pub bcrypt_liquidity_manager_password_hash: Option<bcrypt::HashParts>,
200 pub network: Network,
201 pub num_route_hints: u32,
202 pub default_routing_fees: PaymentFee,
203 pub default_transaction_fees: PaymentFee,
204 pub iroh_listen: Option<SocketAddr>,
205 pub iroh_dns: Option<SafeUrl>,
206 pub iroh_relays: Vec<SafeUrl>,
207 pub skip_setup: bool,
208 pub metrics_listen: SocketAddr,
209}