cdk_mintd/
setup.rs

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