1use crate::key_utils::Secp256k1PublicKey;
2use std::path::PathBuf;
3
4#[cfg(feature = "bitcoin-core-sv2")]
5use crate::config_helpers::opt_path_from_toml;
6
7#[cfg(feature = "bitcoin-core-sv2")]
8use bitcoin_core_sv2::common::BitcoinCoreVersion;
9
10#[derive(Clone, Debug, serde::Deserialize)]
12#[serde(rename_all = "lowercase")]
13pub enum BitcoinNetwork {
14 Mainnet,
15 Testnet4,
16 Signet,
17 Regtest,
18}
19
20#[cfg(feature = "bitcoin-core-sv2")]
21fn deserialize_bitcoin_core_version<'de, D>(deserializer: D) -> Result<BitcoinCoreVersion, D::Error>
22where
23 D: serde::Deserializer<'de>,
24{
25 let major = <u8 as serde::Deserialize>::deserialize(deserializer)?;
26 BitcoinCoreVersion::try_from(major).map_err(|unsupported| {
27 serde::de::Error::custom(format!(
28 "unsupported Bitcoin Core IPC version: {unsupported}. expected 30 or 31"
29 ))
30 })
31}
32
33impl BitcoinNetwork {
34 fn subdir(&self) -> Option<&'static str> {
37 match self {
38 BitcoinNetwork::Mainnet => None,
39 BitcoinNetwork::Testnet4 => Some("testnet4"),
40 BitcoinNetwork::Signet => Some("signet"),
41 BitcoinNetwork::Regtest => Some("regtest"),
42 }
43 }
44}
45
46fn default_bitcoin_data_dir() -> Option<PathBuf> {
48 #[cfg(target_os = "linux")]
49 {
50 dirs::home_dir().map(|h| h.join(".bitcoin"))
51 }
52 #[cfg(target_os = "macos")]
53 {
54 dirs::home_dir().map(|h| h.join("Library/Application Support/Bitcoin"))
55 }
56 #[cfg(not(any(target_os = "linux", target_os = "macos")))]
57 {
58 None
59 }
60}
61
62pub fn resolve_ipc_socket_path(
67 network: &BitcoinNetwork,
68 data_dir: Option<PathBuf>,
69) -> Option<PathBuf> {
70 let base_dir = data_dir.or_else(default_bitcoin_data_dir)?;
71
72 Some(match network.subdir() {
73 Some(subdir) => base_dir.join(subdir).join("node.sock"),
74 None => base_dir.join("node.sock"),
75 })
76}
77
78#[derive(Clone, Debug, serde::Deserialize)]
81pub enum TemplateProviderType {
82 Sv2Tp {
83 address: String,
84 public_key: Option<Secp256k1PublicKey>,
85 },
86 #[cfg(feature = "bitcoin-core-sv2")]
87 BitcoinCoreIpc {
88 #[serde(deserialize_with = "deserialize_bitcoin_core_version")]
90 version: BitcoinCoreVersion,
91 network: BitcoinNetwork,
93 #[serde(default, deserialize_with = "opt_path_from_toml")]
95 data_dir: Option<PathBuf>,
96 fee_threshold: u64,
97 min_interval: u8,
98 },
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn network_with_data_dir_mainnet() {
107 let result =
108 resolve_ipc_socket_path(&BitcoinNetwork::Mainnet, Some(PathBuf::from("/data")));
109 assert_eq!(result, Some(PathBuf::from("/data/node.sock")));
110 }
111
112 #[test]
113 fn network_with_data_dir_regtest() {
114 let result =
115 resolve_ipc_socket_path(&BitcoinNetwork::Regtest, Some(PathBuf::from("/data")));
116 assert_eq!(result, Some(PathBuf::from("/data/regtest/node.sock")));
117 }
118
119 #[test]
120 fn network_with_data_dir_signet() {
121 let result = resolve_ipc_socket_path(&BitcoinNetwork::Signet, Some(PathBuf::from("/data")));
122 assert_eq!(result, Some(PathBuf::from("/data/signet/node.sock")));
123 }
124
125 #[test]
126 fn network_with_data_dir_testnet4() {
127 let result =
128 resolve_ipc_socket_path(&BitcoinNetwork::Testnet4, Some(PathBuf::from("/data")));
129 assert_eq!(result, Some(PathBuf::from("/data/testnet4/node.sock")));
130 }
131
132 #[test]
133 fn missing_data_dir_uses_os_default() {
134 let result = resolve_ipc_socket_path(&BitcoinNetwork::Regtest, None);
137 #[cfg(any(target_os = "linux", target_os = "macos"))]
138 assert!(result.is_some());
139 #[cfg(not(any(target_os = "linux", target_os = "macos")))]
140 assert!(result.is_none());
141 }
142
143 #[cfg(feature = "bitcoin-core-sv2")]
144 #[test]
145 fn bitcoin_core_version_accepts_30_and_31() {
146 assert!(matches!(
147 BitcoinCoreVersion::try_from(30),
148 Ok(BitcoinCoreVersion::V30X)
149 ));
150 assert!(matches!(
151 BitcoinCoreVersion::try_from(31),
152 Ok(BitcoinCoreVersion::V31X)
153 ));
154 }
155
156 #[cfg(feature = "bitcoin-core-sv2")]
157 #[test]
158 fn bitcoin_core_version_rejects_unsupported_values() {
159 assert!(BitcoinCoreVersion::try_from(29).is_err());
160 assert!(BitcoinCoreVersion::try_from(32).is_err());
161 }
162}