Skip to main content

bark/
config.rs

1
2use std::fmt;
3use std::path::{Path, PathBuf};
4
5use anyhow::Context;
6use bitcoin::{FeeRate, Network};
7
8use bitcoin_ext::{BlockDelta, BlockHeight};
9
10use crate::chain::ChainSourceSpec;
11
12
13/// Networks bark can be used on
14#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub enum BarkNetwork {
16	/// Bitcoin's mainnet
17	Mainnet,
18	/// The official Bitcoin Core signet
19	Signet,
20	/// Mutinynet
21	Mutinynet,
22	/// Any regtest network
23	Regtest,
24}
25
26impl BarkNetwork {
27	/// Map to the [Network] types
28	pub fn as_bitcoin(&self) -> Network {
29		match self {
30			Self::Mainnet => Network::Bitcoin,
31			Self::Signet => Network::Signet,
32			Self::Mutinynet => Network::Signet,
33			Self::Regtest => Network::Regtest,
34		}
35	}
36}
37
38impl fmt::Display for BarkNetwork {
39	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40	    match self {
41			Self::Mainnet => f.write_str("mainnet"),
42			Self::Signet => f.write_str("signet"),
43			Self::Mutinynet => f.write_str("mutinynet"),
44			Self::Regtest => f.write_str("regtest"),
45		}
46	}
47}
48
49impl fmt::Debug for BarkNetwork {
50	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51		fmt::Display::fmt(self, f)
52	}
53}
54
55/// Configuration of the Bark wallet.
56///
57/// - [Config::esplora_address] or [Config::bitcoind_address] must be provided.
58/// - If you use [Config::bitcoind_address], you must also provide:
59///   - [Config::bitcoind_cookiefile] or
60///   - [Config::bitcoind_user] and [Config::bitcoind_pass]
61/// - Other optional fields can be omitted.
62///
63/// # Example
64/// Configure the wallet using defaults, then override endpoints for public signet:
65///
66/// ```rust
67/// use bark::Config;
68///
69/// let cfg = Config {
70///   server_address: "https://ark.signet.2nd.dev".into(),
71///   esplora_address: Some("https://esplora.signet.2nd.dev".into()),
72///   ..Config::network_default(bitcoin::Network::Bitcoin)
73/// };
74/// // cfg now has all other fields from the default configuration
75/// ```
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct Config {
78	/// The address of your ark server.
79	pub server_address: String,
80
81	/// An access token used to access a private server
82	pub server_access_token: Option<String>,
83
84	/// Client identifier sent on every RPC to the Ark server (as the
85	/// `x-user-agent` header) so server-side telemetry can attribute traffic
86	/// per implementation.
87	///
88	/// Defaults to `bark/<version>` when unset. Integrators embedding bark
89	/// (FFI bindings, WASM wallets, custom apps) should set their own value,
90	/// e.g. `"aqua/1.4.2"`.
91	///
92	/// Format: `<name>/<version>`. The name must be 1-32 chars of lowercase
93	/// ASCII alphanumeric, `-`, or `_`. Anything else (uppercase, missing
94	/// slash, invalid chars, too long) gets the RPC rejected by the server
95	/// with `invalid_argument`.
96	pub user_agent: Option<String>,
97
98	/// The address of the Esplora HTTP REST server to use.
99	///
100	/// Either this or the `bitcoind_address` field has to be provided.
101	pub esplora_address: Option<String>,
102
103	/// The address of the bitcoind RPC server to use.
104	///
105	/// Either this or the `esplora_address` field has to be provided.
106	/// Either `bitcoind_cookiefile` or `bitcoind_user` and `bitcoind_pass` has to be provided.
107	pub bitcoind_address: Option<String>,
108
109	/// The path to the bitcoind rpc cookie file.
110	///
111	/// Only used with `bitcoind_address`.
112	pub bitcoind_cookiefile: Option<PathBuf>,
113
114	/// The bitcoind RPC username.
115	///
116	/// Only used with `bitcoind_address`.
117	pub bitcoind_user: Option<String>,
118
119	/// The bitcoind RPC password.
120	///
121	/// Only used with `bitcoind_address`.
122	pub bitcoind_pass: Option<String>,
123
124	/// The number of blocks before expiration to refresh vtxos.
125	///
126	/// Default value: 144 (24h) for mainnet, 12 for testnets
127	pub vtxo_refresh_expiry_threshold: BlockHeight,
128
129	/// An upper limit of the number of blocks we expect to need to
130	/// safely exit the vtxos.
131	///
132	/// Default value: 12
133	pub vtxo_exit_margin: BlockDelta,
134
135	/// The number of blocks to claim a HTLC-recv VTXO.
136	///
137	/// Default value: 18
138	pub htlc_recv_claim_delta: BlockDelta,
139
140	/// Maximum number of retry attempts when claiming a Lightning receive
141	/// against the server fails. After this budget is exhausted, the HTLC-recv
142	/// VTXOs will be exited on-chain.
143	///
144	/// Default value: 5
145	pub lightning_receive_claim_retries: u8,
146
147	/// Optional SOCKS5 proxy URL for network traffic.
148	///
149	/// The proxy is automatically bypassed for localhost addresses
150	/// (127.0.0.1, localhost, ::1), so a local bitcoind works without
151	/// extra configuration.
152	///
153	/// Use `socks5h://` to resolve DNS through the proxy which is required for .onion addresses
154	/// and to prevent DNS leaks. We don't allow `socks5://` to be used to preserve privacy.
155	///
156	/// Example: `socks5h://127.0.0.1:9050` for a local Tor daemon.
157	#[cfg(feature = "socks5-proxy")]
158	pub socks5_proxy: Option<String>,
159
160	/// A fallback fee rate to use in sat/kWu when we fail to retrieve a fee rate from the
161	/// configured bitcoind/esplora connection.
162	///
163	/// Example for 1 sat/vB: --fallback-fee-rate 250
164	pub fallback_fee_rate: Option<FeeRate>,
165
166	/// The number of confirmations required before considering a round tx
167	/// fully confirmed
168	///
169	/// Default value: 6 for mainnet, 2 for testnets
170	pub round_tx_required_confirmations: BlockHeight,
171
172	/// The number of confirmations required before considering an offboard tx
173	/// confirmed. If set to 0, offboard movements are marked as successful
174	/// immediately without waiting for confirmation.
175	///
176	/// Default value: 2 for mainnet
177	pub offboard_required_confirmations: BlockHeight,
178
179	/// Daemon sync interval in seconds for periodic tasks (onchain, exits,
180	/// boards, offboards, maintenance, rounds, mailbox).
181	///
182	/// Default value: 60
183	pub daemon_sync_interval_secs: u64,
184
185	/// When set, the daemon skips all automatic wallet syncing — startup
186	/// sync, the fast/slow sync intervals, round event subscription, and
187	/// the mailbox subscription. Only the server connection heartbeat
188	/// keeps running. The operator is responsible for triggering syncs
189	/// via the REST API (e.g. `POST /sync`).
190	///
191	/// Default value: false
192	pub daemon_manual_sync: bool,
193}
194
195impl Config {
196	/// A network-dependent default config that sets some useful defaults
197	///
198	/// The [Default::default] provides a sane default for mainnet
199	pub fn network_default(network: Network) -> Self {
200		let mut ret = Self {
201			server_address: "http://127.0.0.1:3535".to_owned(),
202			server_access_token: None,
203			user_agent: None,
204			esplora_address: None,
205			bitcoind_address: None,
206			bitcoind_cookiefile: None,
207			bitcoind_user: None,
208			bitcoind_pass: None,
209			#[cfg(feature = "socks5-proxy")]
210			socks5_proxy: None,
211			vtxo_refresh_expiry_threshold: 144,
212			vtxo_exit_margin: 12,
213			htlc_recv_claim_delta: 18,
214			lightning_receive_claim_retries: 5,
215			fallback_fee_rate: Some(FeeRate::from_sat_per_vb_u32(2)),
216			round_tx_required_confirmations: 1,
217			offboard_required_confirmations: 2,
218			daemon_sync_interval_secs: 60,
219			daemon_manual_sync: false,
220		};
221
222		if network != Network::Bitcoin {
223			ret.vtxo_refresh_expiry_threshold = 12;
224			ret.fallback_fee_rate = Some(FeeRate::from_sat_per_vb_u32(1));
225			ret.round_tx_required_confirmations = 1;
226			ret.offboard_required_confirmations = 0;
227		}
228
229		ret
230	}
231
232	/// Load config from the config file path, filling missing fields
233	/// from the network default.
234	///
235	/// Config values are loaded in the following priority order (highest to lowest):
236	/// 1. Environment variables with `BARK_` prefix (e.g., `BARK_ESPLORA_ADDRESS`)
237	/// 2. Config file values
238	/// 3. Network defaults
239	pub fn load(network: Network, path: impl AsRef<Path>) -> anyhow::Result<Config> {
240		let default = config::Config::try_from(&Self::network_default(network))
241			.expect("default config failed to deconstruct");
242
243		Ok(config::Config::builder()
244			.add_source(default)
245			.add_source(config::File::from(path.as_ref()).required(false))
246			.add_source(config::Environment::with_prefix("BARK"))
247			.build().context("error building config")?
248			.try_deserialize::<Config>().context("error parsing config")?)
249	}
250
251	/// Creates a [chain::ChainSource] instance to communicate with a chain
252	/// backend from this [Config].
253	pub fn chain_source(&self) -> anyhow::Result<ChainSourceSpec> {
254		if let Some(ref url) = self.esplora_address {
255			Ok(ChainSourceSpec::Esplora {
256				url: url.clone(),
257			})
258		} else if let Some(ref url) = self.bitcoind_address {
259			let auth = if let Some(ref c) = self.bitcoind_cookiefile {
260				bitcoin_ext::rpc::Auth::CookieFile(c.clone())
261			} else {
262				bitcoin_ext::rpc::Auth::UserPass(
263					self.bitcoind_user.clone().context("need bitcoind auth config")?,
264					self.bitcoind_pass.clone().context("need bitcoind auth config")?,
265				)
266			};
267			Ok(ChainSourceSpec::Bitcoind {
268				url: url.clone(),
269				auth,
270			})
271		} else {
272			bail!("Need to either provide esplora or bitcoind info");
273		}
274	}
275}
276