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 #[deprecated(
83 since = "0.2.4",
84 note = "access tokens are not enforced by the server; this field will be removed",
85 )]
86 pub server_access_token: Option<String>,
87
88 /// Client identifier sent on every RPC to the Ark server (as the
89 /// `x-user-agent` header) so server-side telemetry can attribute traffic
90 /// per implementation.
91 ///
92 /// Defaults to `bark/<version>` when unset. Integrators embedding bark
93 /// (FFI bindings, WASM wallets, custom apps) should set their own value,
94 /// e.g. `"aqua/1.4.2"`.
95 ///
96 /// Format: `<name>/<version>`. The name must be 1-32 chars of lowercase
97 /// ASCII alphanumeric, `-`, or `_`. Anything else (uppercase, missing
98 /// slash, invalid chars, too long) gets the RPC rejected by the server
99 /// with `invalid_argument`.
100 pub user_agent: Option<String>,
101
102 /// The address of the Esplora HTTP REST server to use.
103 ///
104 /// Either this or the `bitcoind_address` field has to be provided.
105 pub esplora_address: Option<String>,
106
107 /// The address of the bitcoind RPC server to use.
108 ///
109 /// Either this or the `esplora_address` field has to be provided.
110 /// Either `bitcoind_cookiefile` or `bitcoind_user` and `bitcoind_pass` has to be provided.
111 pub bitcoind_address: Option<String>,
112
113 /// The path to the bitcoind rpc cookie file.
114 ///
115 /// Only used with `bitcoind_address`.
116 pub bitcoind_cookiefile: Option<PathBuf>,
117
118 /// The bitcoind RPC username.
119 ///
120 /// Only used with `bitcoind_address`.
121 pub bitcoind_user: Option<String>,
122
123 /// The bitcoind RPC password.
124 ///
125 /// Only used with `bitcoind_address`.
126 pub bitcoind_pass: Option<String>,
127
128 /// The number of blocks before expiration to refresh vtxos.
129 ///
130 /// Default value: 144 (24h) for mainnet, 12 for testnets
131 pub vtxo_refresh_expiry_threshold: BlockHeight,
132
133 /// An upper limit of the number of blocks we expect to need to
134 /// safely exit the vtxos.
135 ///
136 /// Default value: 12
137 pub vtxo_exit_margin: BlockDelta,
138
139 /// The number of blocks to claim a HTLC-recv VTXO.
140 ///
141 /// Default value: 18
142 pub htlc_recv_claim_delta: BlockDelta,
143
144 /// Maximum number of retry attempts when claiming a Lightning receive
145 /// against the server fails. After this budget is exhausted, the HTLC-recv
146 /// VTXOs will be exited on-chain.
147 ///
148 /// Default value: 5
149 pub lightning_receive_claim_retries: u8,
150
151 /// Optional SOCKS5 proxy URL for network traffic.
152 ///
153 /// The proxy is automatically bypassed for localhost addresses
154 /// (127.0.0.1, localhost, ::1), so a local bitcoind works without
155 /// extra configuration.
156 ///
157 /// Use `socks5h://` to resolve DNS through the proxy which is required for .onion addresses
158 /// and to prevent DNS leaks. We don't allow `socks5://` to be used to preserve privacy.
159 ///
160 /// Example: `socks5h://127.0.0.1:9050` for a local Tor daemon.
161 #[cfg(feature = "socks5-proxy")]
162 pub socks5_proxy: Option<String>,
163
164 /// A fallback fee rate to use in sat/kWu when we fail to retrieve a fee rate from the
165 /// configured bitcoind/esplora connection.
166 ///
167 /// Example for 1 sat/vB: --fallback-fee-rate 250
168 pub fallback_fee_rate: Option<FeeRate>,
169
170 /// The number of confirmations required before considering a round tx
171 /// fully confirmed
172 ///
173 /// Default value: 6 for mainnet, 2 for testnets
174 pub round_tx_required_confirmations: BlockHeight,
175
176 /// The number of confirmations required before considering an offboard tx
177 /// confirmed. If set to 0, offboard movements are marked as successful
178 /// immediately without waiting for confirmation.
179 ///
180 /// Default value: 2 for mainnet
181 pub offboard_required_confirmations: BlockHeight,
182
183 /// How long, in seconds, a broadcast offboard tx may be missing from
184 /// both chain and mempool before the wallet reports the offboard as
185 /// lost. Within the grace period the wallet re-broadcasts the tx
186 /// instead: the chain backend might just be slow or out of sync.
187 ///
188 /// Default value: 3600 (one hour)
189 pub offboard_lost_tx_grace_period_secs: u64,
190
191 /// Daemon sync interval in seconds for periodic tasks (onchain, exits,
192 /// boards, offboards, maintenance, rounds, mailbox).
193 ///
194 /// Default value: 60
195 pub daemon_sync_interval_secs: u64,
196
197 /// When set, the daemon skips all automatic wallet syncing — startup
198 /// sync, the fast/slow sync intervals, round event subscription, and
199 /// the mailbox subscription. Only the server connection heartbeat
200 /// keeps running. The operator is responsible for triggering syncs
201 /// via the REST API (e.g. `POST /sync`).
202 ///
203 /// Default value: false
204 pub daemon_manual_sync: bool,
205}
206
207impl Config {
208 /// A network-dependent default config that sets some useful defaults
209 ///
210 /// The [Default::default] provides a sane default for mainnet
211 pub fn network_default(network: Network) -> Self {
212 #[allow(deprecated)]
213 let mut ret = Self {
214 server_address: "http://127.0.0.1:3535".to_owned(),
215 server_access_token: None,
216 user_agent: None,
217 esplora_address: None,
218 bitcoind_address: None,
219 bitcoind_cookiefile: None,
220 bitcoind_user: None,
221 bitcoind_pass: None,
222 #[cfg(feature = "socks5-proxy")]
223 socks5_proxy: None,
224 vtxo_refresh_expiry_threshold: 144,
225 vtxo_exit_margin: 12,
226 htlc_recv_claim_delta: 18,
227 lightning_receive_claim_retries: 5,
228 fallback_fee_rate: Some(FeeRate::from_sat_per_vb_u32(2)),
229 round_tx_required_confirmations: 1,
230 offboard_required_confirmations: 2,
231 offboard_lost_tx_grace_period_secs: 3600,
232 daemon_sync_interval_secs: 60,
233 daemon_manual_sync: false,
234 };
235
236 if network != Network::Bitcoin {
237 ret.vtxo_refresh_expiry_threshold = 12;
238 ret.fallback_fee_rate = Some(FeeRate::from_sat_per_vb_u32(1));
239 ret.round_tx_required_confirmations = 1;
240 ret.offboard_required_confirmations = 0;
241 }
242
243 ret
244 }
245
246 /// Load config from the config file path, filling missing fields
247 /// from the network default.
248 ///
249 /// Config values are loaded in the following priority order (highest to lowest):
250 /// 1. Environment variables with `BARK_` prefix (e.g., `BARK_ESPLORA_ADDRESS`)
251 /// 2. Config file values
252 /// 3. Network defaults
253 pub fn load(network: Network, path: impl AsRef<Path>) -> anyhow::Result<Config> {
254 let default = config::Config::try_from(&Self::network_default(network))
255 .expect("default config failed to deconstruct");
256
257 Ok(config::Config::builder()
258 .add_source(default)
259 .add_source(config::File::from(path.as_ref()).required(false))
260 .add_source(config::Environment::with_prefix("BARK"))
261 .build().context("error building config")?
262 .try_deserialize::<Config>().context("error parsing config")?)
263 }
264
265 /// Creates a [crate::chain::ChainSource] instance to communicate with a chain
266 /// backend from this [Config].
267 pub fn chain_source(&self) -> anyhow::Result<ChainSourceSpec> {
268 if let Some(ref url) = self.esplora_address {
269 Ok(ChainSourceSpec::Esplora {
270 url: url.clone(),
271 })
272 } else if let Some(ref url) = self.bitcoind_address {
273 let auth = if let Some(ref c) = self.bitcoind_cookiefile {
274 bitcoin_ext::rpc::Auth::CookieFile(c.clone())
275 } else {
276 bitcoin_ext::rpc::Auth::UserPass(
277 self.bitcoind_user.clone().context("need bitcoind auth config")?,
278 self.bitcoind_pass.clone().context("need bitcoind auth config")?,
279 )
280 };
281 Ok(ChainSourceSpec::Bitcoind {
282 url: url.clone(),
283 auth,
284 })
285 } else {
286 bail!("Need to either provide esplora or bitcoind info");
287 }
288 }
289}
290