1#[cfg(feature = "fakewallet")]
2use std::collections::HashMap;
3#[cfg(feature = "fakewallet")]
4use std::collections::HashSet;
5use std::path::Path;
6use std::sync::Arc;
7
8#[cfg(feature = "cln")]
9use anyhow::anyhow;
10#[cfg(any(feature = "lnbits", feature = "lnd"))]
11use anyhow::bail;
12use async_trait::async_trait;
13#[cfg(feature = "fakewallet")]
14use bip39::rand::{thread_rng, Rng};
15use cdk::cdk_database::MintKVStore;
16use cdk::cdk_payment::MintPayment;
17use cdk::nuts::CurrencyUnit;
18#[cfg(any(
19 feature = "lnbits",
20 feature = "cln",
21 feature = "lnd",
22 feature = "ldk-node",
23 feature = "fakewallet"
24))]
25use cdk::types::FeeReserve;
26
27use crate::config::{self, Settings};
28#[cfg(feature = "cln")]
29use crate::expand_path;
30
31#[async_trait]
32pub trait LnBackendSetup {
33 async fn setup(
34 &self,
35 settings: &Settings,
36 unit: CurrencyUnit,
37 runtime: Option<std::sync::Arc<tokio::runtime::Runtime>>,
38 work_dir: &Path,
39 kv_store: Option<Arc<dyn MintKVStore<Err = cdk::cdk_database::Error> + Send + Sync>>,
40 ) -> anyhow::Result<impl MintPayment>;
41}
42
43#[cfg(feature = "cln")]
44#[async_trait]
45impl LnBackendSetup for config::Cln {
46 async fn setup(
47 &self,
48 _settings: &Settings,
49 _unit: CurrencyUnit,
50 _runtime: Option<std::sync::Arc<tokio::runtime::Runtime>>,
51 _work_dir: &Path,
52 kv_store: Option<Arc<dyn MintKVStore<Err = cdk::cdk_database::Error> + Send + Sync>>,
53 ) -> anyhow::Result<cdk_cln::Cln> {
54 if self.rpc_path.as_os_str().is_empty() {
56 return Err(anyhow!(
57 "CLN rpc_path must be set via config or CDK_MINTD_CLN_RPC_PATH env var"
58 ));
59 }
60
61 let cln_socket = expand_path(
62 self.rpc_path
63 .to_str()
64 .ok_or(anyhow!("cln socket not defined"))?,
65 )
66 .ok_or(anyhow!("cln socket not defined"))?;
67
68 let fee_reserve = FeeReserve {
69 min_fee_reserve: self.reserve_fee_min,
70 percent_fee_reserve: self.fee_percent,
71 };
72
73 let cln = cdk_cln::Cln::new(
74 cln_socket,
75 fee_reserve,
76 kv_store.expect("Cln needs kv store"),
77 )
78 .await?;
79
80 Ok(cln)
81 }
82}
83
84#[cfg(feature = "lnbits")]
85#[async_trait]
86impl LnBackendSetup for config::LNbits {
87 async fn setup(
88 &self,
89 _settings: &Settings,
90 _unit: CurrencyUnit,
91 _runtime: Option<std::sync::Arc<tokio::runtime::Runtime>>,
92 _work_dir: &Path,
93 _kv_store: Option<Arc<dyn MintKVStore<Err = cdk::cdk_database::Error> + Send + Sync>>,
94 ) -> anyhow::Result<cdk_lnbits::LNbits> {
95 if self.admin_api_key.is_empty() {
97 bail!("LNbits admin_api_key must be set via config or CDK_MINTD_LNBITS_ADMIN_API_KEY env var");
98 }
99 if self.invoice_api_key.is_empty() {
100 bail!("LNbits invoice_api_key must be set via config or CDK_MINTD_LNBITS_INVOICE_API_KEY env var");
101 }
102 if self.lnbits_api.is_empty() {
103 bail!(
104 "LNbits lnbits_api must be set via config or CDK_MINTD_LNBITS_LNBITS_API env var"
105 );
106 }
107
108 let admin_api_key = &self.admin_api_key;
109 let invoice_api_key = &self.invoice_api_key;
110
111 let fee_reserve = FeeReserve {
112 min_fee_reserve: self.reserve_fee_min,
113 percent_fee_reserve: self.fee_percent,
114 };
115
116 let lnbits = cdk_lnbits::LNbits::new(
117 admin_api_key.clone(),
118 invoice_api_key.clone(),
119 self.lnbits_api.clone(),
120 fee_reserve,
121 )
122 .await?;
123
124 lnbits.subscribe_ws().await?;
126
127 Ok(lnbits)
128 }
129}
130
131#[cfg(feature = "lnd")]
132#[async_trait]
133impl LnBackendSetup for config::Lnd {
134 async fn setup(
135 &self,
136 _settings: &Settings,
137 _unit: CurrencyUnit,
138 _runtime: Option<std::sync::Arc<tokio::runtime::Runtime>>,
139 _work_dir: &Path,
140 kv_store: Option<Arc<dyn MintKVStore<Err = cdk::cdk_database::Error> + Send + Sync>>,
141 ) -> anyhow::Result<cdk_lnd::Lnd> {
142 if self.address.is_empty() {
144 bail!("LND address must be set via config or CDK_MINTD_LND_ADDRESS env var");
145 }
146 if self.cert_file.as_os_str().is_empty() {
147 bail!("LND cert_file must be set via config or CDK_MINTD_LND_CERT_FILE env var");
148 }
149 if self.macaroon_file.as_os_str().is_empty() {
150 bail!(
151 "LND macaroon_file must be set via config or CDK_MINTD_LND_MACAROON_FILE env var"
152 );
153 }
154
155 let address = &self.address;
156 let cert_file = &self.cert_file;
157 let macaroon_file = &self.macaroon_file;
158
159 let fee_reserve = FeeReserve {
160 min_fee_reserve: self.reserve_fee_min,
161 percent_fee_reserve: self.fee_percent,
162 };
163
164 let lnd = cdk_lnd::Lnd::new(
165 address.to_string(),
166 cert_file.clone(),
167 macaroon_file.clone(),
168 fee_reserve,
169 kv_store.expect("Lnd needs kv store"),
170 )
171 .await?;
172
173 Ok(lnd)
174 }
175}
176
177#[cfg(feature = "fakewallet")]
178#[async_trait]
179impl LnBackendSetup for config::FakeWallet {
180 async fn setup(
181 &self,
182 _settings: &Settings,
183 unit: CurrencyUnit,
184 _runtime: Option<std::sync::Arc<tokio::runtime::Runtime>>,
185 _work_dir: &Path,
186 _kv_store: Option<Arc<dyn MintKVStore<Err = cdk::cdk_database::Error> + Send + Sync>>,
187 ) -> anyhow::Result<cdk_fake_wallet::FakeWallet> {
188 let fee_reserve = FeeReserve {
189 min_fee_reserve: self.reserve_fee_min,
190 percent_fee_reserve: self.fee_percent,
191 };
192
193 let mut rng = thread_rng();
195 let delay_time = rng.gen_range(self.min_delay_time..=self.max_delay_time);
196
197 let fake_wallet = cdk_fake_wallet::FakeWallet::new(
198 fee_reserve,
199 HashMap::default(),
200 HashSet::default(),
201 delay_time,
202 unit,
203 );
204
205 Ok(fake_wallet)
206 }
207}
208
209#[cfg(feature = "grpc-processor")]
210#[async_trait]
211impl LnBackendSetup for config::GrpcProcessor {
212 async fn setup(
213 &self,
214 _settings: &Settings,
215 _unit: CurrencyUnit,
216 _runtime: Option<std::sync::Arc<tokio::runtime::Runtime>>,
217 _work_dir: &Path,
218 _kv_store: Option<Arc<dyn MintKVStore<Err = cdk::cdk_database::Error> + Send + Sync>>,
219 ) -> anyhow::Result<cdk_payment_processor::PaymentProcessorClient> {
220 let payment_processor = cdk_payment_processor::PaymentProcessorClient::new(
221 &self.addr,
222 self.port,
223 self.tls_dir.clone(),
224 )
225 .await?;
226
227 Ok(payment_processor)
228 }
229}
230
231#[cfg(feature = "ldk-node")]
232#[async_trait]
233impl LnBackendSetup for config::LdkNode {
234 async fn setup(
235 &self,
236 _settings: &Settings,
237 _unit: CurrencyUnit,
238 runtime: Option<std::sync::Arc<tokio::runtime::Runtime>>,
239 work_dir: &Path,
240 _kv_store: Option<Arc<dyn MintKVStore<Err = cdk::cdk_database::Error> + Send + Sync>>,
241 ) -> anyhow::Result<cdk_ldk_node::CdkLdkNode> {
242 use std::net::SocketAddr;
243
244 use bitcoin::Network;
245
246 let fee_reserve = FeeReserve {
247 min_fee_reserve: self.reserve_fee_min,
248 percent_fee_reserve: self.fee_percent,
249 };
250
251 let network = match self
253 .bitcoin_network
254 .as_ref()
255 .map(|n| n.to_lowercase())
256 .as_deref()
257 .unwrap_or("regtest")
258 {
259 "mainnet" | "bitcoin" => Network::Bitcoin,
260 "testnet" => Network::Testnet,
261 "signet" => Network::Signet,
262 _ => Network::Regtest,
263 };
264
265 let chain_source = match self
267 .chain_source_type
268 .as_ref()
269 .map(|s| s.to_lowercase())
270 .as_deref()
271 .unwrap_or("esplora")
272 {
273 "bitcoinrpc" => {
274 let host = self
275 .bitcoind_rpc_host
276 .clone()
277 .unwrap_or_else(|| "127.0.0.1".to_string());
278 let port = self.bitcoind_rpc_port.unwrap_or(18443);
279 let user = self
280 .bitcoind_rpc_user
281 .clone()
282 .unwrap_or_else(|| "testuser".to_string());
283 let password = self
284 .bitcoind_rpc_password
285 .clone()
286 .unwrap_or_else(|| "testpass".to_string());
287
288 cdk_ldk_node::ChainSource::BitcoinRpc(cdk_ldk_node::BitcoinRpcConfig {
289 host,
290 port,
291 user,
292 password,
293 })
294 }
295 _ => {
296 let esplora_url = self
297 .esplora_url
298 .clone()
299 .unwrap_or_else(|| "https://mutinynet.com/api".to_string());
300 cdk_ldk_node::ChainSource::Esplora(esplora_url)
301 }
302 };
303
304 let gossip_source = match self.rgs_url.clone() {
306 Some(rgs_url) => cdk_ldk_node::GossipSource::RapidGossipSync(rgs_url),
307 None => cdk_ldk_node::GossipSource::P2P,
308 };
309
310 let storage_dir_path = if let Some(dir_path) = &self.storage_dir_path {
312 dir_path.clone()
313 } else {
314 let mut work_dir = work_dir.to_path_buf();
315 work_dir.push("ldk-node");
316 work_dir.to_string_lossy().to_string()
317 };
318
319 let host = self
321 .ldk_node_host
322 .clone()
323 .unwrap_or_else(|| "127.0.0.1".to_string());
324 let port = self.ldk_node_port.unwrap_or(8090);
325
326 let socket_addr = SocketAddr::new(host.parse()?, port);
327
328 let listen_address = vec![socket_addr.into()];
332
333 let mut ldk_node = cdk_ldk_node::CdkLdkNode::new(
334 network,
335 chain_source,
336 gossip_source,
337 storage_dir_path,
338 fee_reserve,
339 listen_address,
340 runtime,
341 )?;
342
343 let webserver_addr = if let Some(host) = &self.webserver_host {
345 let port = self.webserver_port.unwrap_or(8091);
346 let socket_addr: SocketAddr = format!("{host}:{port}").parse()?;
347 Some(socket_addr)
348 } else if self.webserver_port.is_some() {
349 let port = self.webserver_port.unwrap_or(8091);
351 let socket_addr: SocketAddr = format!("127.0.0.1:{port}").parse()?;
352 Some(socket_addr)
353 } else {
354 Some(cdk_ldk_node::CdkLdkNode::default_web_addr())
356 };
357
358 println!("webserver: {:?}", webserver_addr);
359
360 ldk_node.set_web_addr(webserver_addr);
361
362 Ok(ldk_node)
363 }
364}