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