bark/
lib.rs

1//! ![bark: Ark on bitcoin](https://gitlab.com/ark-bitcoin/bark/-/raw/master/assets/bark-header-white.jpg)
2//!
3//! <div align="center">
4//! <h1>Bark: Ark on bitcoin</h1>
5//! <p>Fast, low-cost, self-custodial payments on bitcoin.</p>
6//! </div>
7//!
8//! <p align="center">
9//! <br />
10//! <a href="https://docs.second.tech">Docs</a> ·
11//! <a href="https://gitlab.com/ark-bitcoin/bark/-/issues">Issues</a> ·
12//! <a href="https://second.tech">Website</a> ·
13//! <a href="https://blog.second.tech">Blog</a> ·
14//! <a href="https://www.youtube.com/@2ndbtc">YouTube</a>
15//! </p>
16//!
17//! <div align="center">
18//!
19//! [![Release](https://img.shields.io/gitlab/v/release/ark-bitcoin/bark?gitlab_url=https://gitlab.com&sort=semver&label=release)
20//! [![Project Status](https://img.shields.io/badge/status-experimental-red.svg)](https://gitlab.com/ark-bitcoin/bark)
21//! [![License](https://img.shields.io/badge/license-CC0--1.0-blue.svg)](https://gitlab.com/ark-bitcoin/bark/-/blob/master/LICENSE)
22//! [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen?logo=git)](https://gitlab.com/ark-bitcoin/bark/-/blob/master/CONTRIBUTING.md)
23//! [![Community](https://img.shields.io/badge/community-forum-blue?logo=discourse)](https://community.second.tech)
24//!
25//! </div>
26//! <br />
27//!
28//! Bark is an implementation of the Ark protocol on bitcoin, led by [Second](https://second.tech).
29//!
30//! # A tour of Bark
31//!
32//! Integrating the Ark-protocol offers
33//!
34//! - 🏃‍♂️ **Smooth boarding**: No channels to open, no on-chain setup required—create a wallet and start transacting
35//! - 🤌 **Simplified UX**: Send and receive without managing channels, liquidity, or routing
36//! - 🌐 **Universal payments**: Send Ark, Lightning, and on-chain payments from a single off-chain balance
37//! - 🔌 **Easier integration**: Client-server architecture reduces complexity compared to P2P protocols
38//! - 💸 **Lower costs**: Instant payments at a fraction of on-chain fees
39//! - 🔒 **Self-custodial**: Users maintain full control of their funds at all times
40//!
41//! This guide puts focus on how to use the Rust-API and assumes
42//! some basic familiarity with the Ark protocol. We refer to the
43//! [protocol docs](http://docs.second.tech/ark-protocol) for an introduction.
44//!
45//! ## Creating an Ark wallet
46//!
47//! The user experience of setting up an Ark wallet is pretty similar
48//! to setting up an onchain wallet. You need to provide a [bip39::Mnemonic] which
49//! can be used to recover funds. Typically, most apps request the user
50//! to write down the mnemonic or ensure they use another method for a secure back-up.
51//!
52//! The user can select an Ark server and a [chain::ChainSource] as part of
53//! the configuration. The example below configures
54//!
55//! You will also need a place to store all [ark::Vtxo]s on the users device.
56//! We have implemented [SqliteClient] which is a sane default on most devices.
57//! However, it is possible to implement a [BarkPersister] if you have other
58//! requirements.
59//!
60//! The code-snippet below shows how you can create a [Wallet].
61//!
62//! ```no_run
63//! use std::path::PathBuf;
64//! use std::sync::Arc;
65//! use tokio::fs;
66//! use bark::{Config, onchain, SqliteClient, Wallet};
67//!
68//! const MNEMONIC_FILE : &str = "mnemonic";
69//! const DB_FILE: &str = "db.sqlite";
70//!
71//! #[tokio::main]
72//! async fn main() {
73//! 	// Pick the bitcoin network that will be used
74//! 	let network = bitcoin::Network::Signet;
75//!
76//! 	// Configure the wallet
77//! 	let config = Config {
78//! 		server_address: String::from("https://ark.signet.2nd.dev"),
79//! 		esplora_address: Some(String::from("https://esplora.signet.2nd.dev")),
80//! 		..Config::network_default(network)
81//! 	};
82//!
83//!
84//! 	// Create a sqlite database
85//! 	let datadir = PathBuf::from("./bark");
86//! 	let db = Arc::new(SqliteClient::open(datadir.join(DB_FILE)).unwrap());
87//!
88//! 	// Generate and seed and store it somewhere
89//! 	let mnemonic = bip39::Mnemonic::generate(12).expect("12 is valid");
90//! 	fs::write(datadir.join(MNEMONIC_FILE), mnemonic.to_string().as_bytes()).await.unwrap();
91//!
92//! 	let wallet = Wallet::create(
93//! 		&mnemonic,
94//! 		network,
95//! 		config,
96//! 		db,
97//! 		false
98//! 	).await.unwrap();
99//! }
100//! ```
101//!
102//! ## Opening an existing Ark wallet
103//!
104//! The [Wallet] can be opened again by providing the [bip39::Mnemonic] and
105//! the [BarkPersister] again. Note, that [SqliteClient] implements the [BarkPersister]-trait.
106//!
107//! ```no_run
108//! # use std::sync::Arc;
109//! # use std::path::PathBuf;
110//! # use std::str::FromStr;
111//! #
112//! # use bip39;
113//! # use tokio::fs;
114//! #
115//! # use bark::{Config, SqliteClient, Wallet};
116//! #
117//! const MNEMONIC_FILE : &str = "mnemonic";
118//! const DB_FILE: &str = "db.sqlite";
119//!
120//! #[tokio::main]
121//! async fn main() {
122//! 	let datadir = PathBuf::from("./bark");
123//! 	let config = Config {
124//! 		server_address: String::from("https://ark.signet.2nd.dev"),
125//! 		esplora_address: Some(String::from("https://esplora.signet.2nd.dev")),
126//! 		..Config::network_default(bitcoin::Network::Signet)
127//! 	};
128//!
129//! 	let db = Arc::new(SqliteClient::open(datadir.join(DB_FILE)).unwrap());
130//! 	let mnemonic_str = fs::read_to_string(datadir.join(DB_FILE)).await.unwrap();
131//! 	let mnemonic = bip39::Mnemonic::from_str(&mnemonic_str).unwrap();
132//! 	let wallet = Wallet::open(&mnemonic, db, config).await.unwrap();
133//! }
134//! ```
135//!
136//! ## Receiving coins
137//!
138//! For the time being we haven't implemented an Ark address type (yet). You
139//! can send funds directly to a public key.
140//!
141//! If you are on signet and your Ark server is [https://ark.signet.2nd.dev](https://ark.signet.2nd.dev),
142//! you can request some sats from our [faucet](https://signet.2nd.dev).
143//!
144//! ```no_run
145//! # use std::sync::Arc;
146//! # use std::str::FromStr;
147//! # use std::path::PathBuf;
148//! #
149//! # use tokio::fs;
150//! #
151//! # use bark::{Config, Wallet, SqliteClient};
152//! #
153//! # const MNEMONIC_FILE : &str = "mnemonic";
154//! # const DB_FILE: &str = "db.sqlite";
155//! #
156//! # async fn get_wallet() -> Wallet {
157//! 	#   let datadir = PathBuf::from("./bark");
158//! 	#   let config = Config::network_default(bitcoin::Network::Signet);
159//! 	#
160//! 	#   let db = Arc::new(SqliteClient::open(datadir.join(DB_FILE)).unwrap());
161//! 	#   let mnemonic_str = fs::read_to_string(datadir.join(DB_FILE)).await.unwrap();
162//! 	#   let mnemonic = bip39::Mnemonic::from_str(&mnemonic_str).unwrap();
163//! 	#   Wallet::open(&mnemonic, db, config).await.unwrap()
164//! 	# }
165//! #
166//!
167//! #[tokio::main]
168//! async fn main() -> anyhow::Result<()> {
169//! 	let wallet = get_wallet().await;
170//! 	let address: ark::Address = wallet.new_address().await?;
171//! 	Ok(())
172//! }
173//! ```
174//!
175//! ## Inspecting the wallet
176//!
177//! An Ark wallet contains [ark::Vtxo]s. These are just like normal utxos
178//! in a bitcoin wallet. They just haven't been confirmed on chain (yet).
179//! However, the user remains in full control of the funds and can perform
180//! a unilateral exit at any time.
181//!
182//! The snippet below shows how you can inspect your [WalletVtxo]s.
183//!
184//! ```no_run
185//! # use std::sync::Arc;
186//! # use std::str::FromStr;
187//! # use std::path::PathBuf;
188//! #
189//! # use tokio::fs;
190//! #
191//! # use bark::{Config, SqliteClient, Wallet};
192//! #
193//! # const MNEMONIC_FILE : &str = "mnemonic";
194//! # const DB_FILE: &str = "db.sqlite";
195//! #
196//! # async fn get_wallet() -> Wallet {
197//! 	#   let datadir = PathBuf::from("./bark");
198//! 	#
199//! 	#   let db = Arc::new(SqliteClient::open(datadir.join(DB_FILE)).unwrap());
200//! 	#   let mnemonic_str = fs::read_to_string(datadir.join(DB_FILE)).await.unwrap();
201//! 	#   let mnemonic = bip39::Mnemonic::from_str(&mnemonic_str).unwrap();
202//! 	#
203//! 	#   let config = Config::network_default(bitcoin::Network::Signet);
204//! 	#
205//! 	#   Wallet::open(&mnemonic, db, config).await.unwrap()
206//! 	# }
207//! #
208//!
209//! #[tokio::main]
210//! async fn main() -> anyhow::Result<()> {
211//! 	let mut wallet = get_wallet().await;
212//!
213//! 	// The vtxo's command doesn't sync your wallet
214//! 	// Make sure your app is synced before inspecting the wallet
215//! 	wallet.sync().await;
216//!
217//! 	let vtxos: Vec<bark::WalletVtxo> = wallet.vtxos().await.unwrap();
218//! 	Ok(())
219//! }
220//! ```
221//!
222//! Use [Wallet::balance] if you are only interested in the balance.
223//!
224//! ## Participating in a round
225//!
226//! You can participate in a round to refresh your coins. Typically,
227//! you want to refresh coins which are soon to expire or you might
228//! want to aggregate multiple small vtxos to keep the cost of exit
229//! under control.
230//!
231//! As a wallet developer you can implement your own refresh strategy.
232//! This gives you full control over which [ark::Vtxo]s are refreshed and
233//! which aren't.
234//!
235//! This example uses [RefreshStrategy::must_refresh] which is a sane
236//! default that selects all [ark::Vtxo]s that must be refreshed.
237//!
238//! ```no_run
239//! # use std::sync::Arc;
240//! # use std::str::FromStr;
241//! # use std::path::PathBuf;
242//! #
243//! # use tokio::fs;
244//! #
245//! # use bark::{Config, Wallet, SqliteClient};
246//! #
247//! # const MNEMONIC_FILE : &str = "mnemonic";
248//! # const DB_FILE: &str = "db.sqlite";
249//! #
250//! # async fn get_wallet() -> Wallet {
251//! 	#   let datadir = PathBuf::from("./bark");
252//! 	#
253//! 	#   let db = Arc::new(SqliteClient::open(datadir.join(DB_FILE)).unwrap());
254//! 	#   let mnemonic_str = fs::read_to_string(datadir.join(DB_FILE)).await.unwrap();
255//! 	#   let mnemonic = bip39::Mnemonic::from_str(&mnemonic_str).unwrap();
256//! 	#
257//! 	#   let config = Config::network_default(bitcoin::Network::Signet);
258//! 	#
259//! 	#   Wallet::open(&mnemonic, db, config).await.unwrap()
260//! 	# }
261//! #
262//! use bark::vtxo::RefreshStrategy;
263//!
264//! #[tokio::main]
265//! async fn main() -> anyhow::Result<()> {
266//! 	let wallet = get_wallet().await;
267//!
268//! 	// Select all vtxos that refresh soon
269//! 	let tip = wallet.chain.tip().await?;
270//! 	let fee_rate = wallet.chain.fee_rates().await.fast;
271//! 	let strategy = RefreshStrategy::must_refresh(&wallet, tip, fee_rate);
272//!
273//! 	let vtxos = wallet.spendable_vtxos_with(&strategy).await?
274//! 		.into_iter().map(|v| v.vtxo).collect::<Vec<_>>();
275//!		wallet.refresh_vtxos(vtxos).await?;
276//! 	Ok(())
277//! }
278//! ```
279
280
281
282pub extern crate ark;
283
284pub extern crate bip39;
285pub extern crate lightning_invoice;
286pub extern crate lnurl as lnurllib;
287
288#[macro_use] extern crate anyhow;
289#[macro_use] extern crate async_trait;
290#[macro_use] extern crate serde;
291
292pub mod chain;
293pub mod exit;
294pub mod movement;
295pub mod onchain;
296pub mod persist;
297pub mod round;
298pub mod subsystem;
299pub mod vtxo;
300
301mod arkoor;
302mod config;
303mod daemon;
304mod lightning;
305mod offboard;
306mod psbtext;
307mod server;
308
309pub use self::arkoor::ArkoorCreateResult;
310pub use self::config::{BarkNetwork, Config};
311pub use self::daemon::DaemonHandle;
312pub use self::persist::sqlite::SqliteClient;
313pub use self::vtxo::WalletVtxo;
314
315use std::collections::{HashMap, HashSet};
316use std::sync::Arc;
317
318use anyhow::{bail, Context};
319use bip39::Mnemonic;
320use bitcoin::{Amount, Network, OutPoint};
321use bitcoin::bip32::{self, ChildNumber, Fingerprint};
322use bitcoin::secp256k1::{self, Keypair, PublicKey};
323use log::{trace, debug, info, warn, error};
324use tokio::sync::RwLock;
325
326use ark::{ArkInfo, ProtocolEncoding, Vtxo, VtxoId, VtxoPolicy, VtxoRequest};
327use ark::address::VtxoDelivery;
328use ark::board::{BoardBuilder, BOARD_FUNDING_TX_VTXO_VOUT};
329use ark::vtxo::{PubkeyVtxoPolicy, VtxoRef};
330use ark::vtxo::policy::signing::VtxoSigner;
331use bitcoin_ext::{BlockHeight, P2TR_DUST, TxStatus};
332use server_rpc::{self as rpc, protos, ServerConnection};
333
334use crate::chain::{ChainSource, ChainSourceSpec};
335use crate::exit::Exit;
336use crate::movement::{Movement, MovementStatus};
337use crate::movement::manager::MovementManager;
338use crate::movement::update::MovementUpdate;
339use crate::onchain::{DaemonizableOnchainWallet, ExitUnilaterally, PreparePsbt, SignPsbt, Utxo};
340use crate::persist::{BarkPersister, RoundStateId};
341use crate::persist::models::{LightningReceive, LightningSend, PendingBoard};
342use crate::round::{RoundParticipation, RoundStatus};
343use crate::subsystem::{ArkoorMovement, BoardMovement, RoundMovement, Subsystem};
344use crate::vtxo::{FilterVtxos, RefreshStrategy, VtxoFilter, VtxoState, VtxoStateKind};
345
346/// Derivation index for Bark usage
347const BARK_PURPOSE_INDEX: u32 = 350;
348/// Derivation index used to generate keypairs to sign VTXOs
349const VTXO_KEYS_INDEX: u32 = 0;
350/// Derivation index used to generate keypair for the mailbox
351const MAILBOX_KEY_INDEX: u32 = 1;
352
353lazy_static::lazy_static! {
354	/// Global secp context.
355	static ref SECP: secp256k1::Secp256k1<secp256k1::All> = secp256k1::Secp256k1::new();
356}
357
358/// The detailled balance of a Lightning receive.
359#[derive(Debug, Clone)]
360pub struct LightningReceiveBalance {
361	/// Sum of all pending lightning invoices
362	pub total: Amount,
363	/// Sum of all invoices for which we received the HTLC VTXOs
364	pub claimable: Amount,
365}
366
367/// The different balances of a Bark wallet.
368#[derive(Debug, Clone)]
369pub struct Balance {
370	/// Coins that are spendable in the Ark, either in-round or out-of-round.
371	pub spendable: Amount,
372	/// Coins that are in the process of being sent over Lightning.
373	pub pending_lightning_send: Amount,
374	/// Coins that are in the process of being received over Lightning.
375	pub claimable_lightning_receive: Amount,
376	/// Coins locked in a round.
377	pub pending_in_round: Amount,
378	/// Coins that are in the process of unilaterally exiting the Ark.
379	/// None if exit subsystem was unavailable
380	pub pending_exit: Option<Amount>,
381	/// Coins that are pending sufficient confirmations from board transactions.
382	pub pending_board: Amount,
383}
384
385pub struct UtxoInfo {
386	pub outpoint: OutPoint,
387	pub amount: Amount,
388	pub confirmation_height: Option<u32>,
389}
390
391impl From<Utxo> for UtxoInfo {
392	fn from(value: Utxo) -> Self {
393		match value {
394			Utxo::Local(o) => UtxoInfo {
395				outpoint: o.outpoint,
396				amount: o.amount,
397				confirmation_height: o.confirmation_height,
398			},
399			Utxo::Exit(e) => UtxoInfo {
400				outpoint: e.vtxo.point(),
401				amount: e.vtxo.amount(),
402				confirmation_height: Some(e.height),
403			},
404		}
405	}
406}
407
408/// Represents an offchain balance structure consisting of available funds, pending amounts in
409/// unconfirmed rounds, and pending exits.
410pub struct OffchainBalance {
411	/// Funds currently available for use. This reflects the spendable balance.
412	pub available: Amount,
413	/// Funds that are pending in unconfirmed operational rounds.
414	pub pending_in_round: Amount,
415	/// Funds being unilaterally exited. These may require more onchain confirmations to become
416	/// available onchain.
417	pub pending_exit: Amount,
418}
419
420/// Read-only properties of the Bark wallet.
421#[derive(Debug, Clone)]
422pub struct WalletProperties {
423	/// The Bitcoin network to run Bark on.
424	///
425	/// Default value: signet.
426	pub network: Network,
427
428	/// The wallet fingerpint
429	///
430	/// Used on wallet loading to check mnemonic correctness
431	pub fingerprint: Fingerprint,
432}
433
434/// Struct representing an extended private key derived from a
435/// wallet's seed, used to derive child VTXO keypairs
436///
437/// The VTXO seed is derived by applying a hardened derivation
438/// step at index 350 from the wallet's seed.
439pub struct WalletSeed {
440	master: bip32::Xpriv,
441	vtxo: bip32::Xpriv,
442}
443
444impl WalletSeed {
445	fn new(network: Network, seed: &[u8; 64]) -> Self {
446		let bark_path = [ChildNumber::from_hardened_idx(BARK_PURPOSE_INDEX).unwrap()];
447		let master = bip32::Xpriv::new_master(network, seed)
448			.expect("invalid seed")
449			.derive_priv(&SECP, &bark_path)
450			.expect("purpose is valid");
451
452		let vtxo_path = [ChildNumber::from_hardened_idx(VTXO_KEYS_INDEX).unwrap()];
453		let vtxo = master.derive_priv(&SECP, &vtxo_path)
454			.expect("vtxo path is valid");
455
456		Self { master, vtxo }
457	}
458
459	fn fingerprint(&self) -> Fingerprint {
460		self.master.fingerprint(&SECP)
461	}
462
463	fn derive_vtxo_keypair(&self, idx: u32) -> Keypair {
464		self.vtxo.derive_priv(&SECP, &[idx.into()]).unwrap().to_keypair(&SECP)
465	}
466
467	#[allow(unused)]
468	fn to_mailbox_keypair(&self) -> Keypair {
469		let mailbox_path = [ChildNumber::from_hardened_idx(MAILBOX_KEY_INDEX).unwrap()];
470		self.master.derive_priv(&SECP, &mailbox_path).unwrap().to_keypair(&SECP)
471	}
472}
473
474/// The central entry point for using this library as an Ark wallet.
475///
476/// Overview
477/// - Wallet encapsulates the complete Ark client implementation:
478///   - address generation (Ark addresses/keys)
479///     - [Wallet::new_address],
480///     - [Wallet::new_address_with_index],
481///     - [Wallet::peak_address],
482///     - [Wallet::validate_arkoor_address]
483///   - boarding onchain funds into Ark from an onchain wallet (see [onchain::OnchainWallet])
484///     - [Wallet::board_amount],
485///     - [Wallet::board_all]
486///   - offboarding Ark funds to move them back onchain
487///     - [Wallet::offboard_vtxos],
488///     - [Wallet::offboard_all]
489///   - sending and receiving Ark payments (including to BOLT11/BOLT12 invoices)
490///     - [Wallet::send_arkoor_payment],
491///     - [Wallet::pay_lightning_invoice],
492///     - [Wallet::pay_lightning_address],
493///     - [Wallet::pay_lightning_offer]
494///   - tracking, selecting, and refreshing VTXOs
495///     - [Wallet::vtxos],
496///     - [Wallet::vtxos_with],
497///     - [Wallet::refresh_vtxos]
498///   - syncing with the Ark server, unilateral exits and performing general maintenance
499///     - [Wallet::maintenance]: Syncs everything offchain-related and refreshes VTXOs where
500///       necessary,
501///     - [Wallet::maintenance_with_onchain]: The same as [Wallet::maintenance] but also syncs the
502///       onchain wallet and unilateral exits,
503///     - [Wallet::maintenance_refresh]: Refreshes VTXOs where necessary without syncing anything,
504///     - [Wallet::sync]: Syncs network fee-rates, ark rounds and arkoor payments,
505///     - [Wallet::sync_exits]: Updates the status of unilateral exits,
506///     - [Wallet::sync_pending_lightning_send_vtxos]: Updates the status of pending lightning payments,
507///     - [Wallet::try_claim_all_lightning_receives]: Wait for payment receipt of all open invoices, then claim them,
508///     - [Wallet::sync_pending_boards]: Registers boards which are available for use
509///       in offchain payments
510///
511/// Key capabilities
512/// - Address management:
513///   - derive and peek deterministic Ark addresses and their indices
514/// - Funds lifecycle:
515///   - board funds from an external onchain wallet onto the Ark
516///   - send out-of-round Ark payments (arkoor)
517///   - offboard funds to onchain addresses
518///   - manage HTLCs and Lightning receives/sends
519/// - VTXO management:
520///   - query spendable and pending VTXOs
521///   - refresh expiring or risky VTXOs
522///   - compute balance broken down by spendable/pending states
523/// - Synchronization and maintenance:
524///   - sync against the Ark server and the onchain source
525///   - reconcile pending rounds, exits, and offchain state
526///   - periodic maintenance helpers (e.g., auto-register boards, refresh policies)
527///
528/// Construction and persistence
529/// - A [Wallet] is opened or created using a mnemonic and a backend implementing [BarkPersister].
530///   - [Wallet::create],
531///   - [Wallet::open]
532/// - Creation allows the use of an optional onchain wallet for boarding and [Exit] functionality.
533///   It also initializes any internal state and connects to the [chain::ChainSource]. See
534///   [onchain::OnchainWallet] for an implementation of an onchain wallet using BDK.
535///   - [Wallet::create_with_onchain],
536///   - [Wallet::open_with_onchain]
537///
538/// Example
539/// ```
540/// # #[cfg(any(test, doc))]
541/// # async fn demo() -> anyhow::Result<()> {
542/// # use std::sync::Arc;
543/// # use bark::{Config, SqliteClient, Wallet};
544/// # use bark::onchain::OnchainWallet;
545/// # use bark::persist::BarkPersister;
546/// # use bark::persist::sqlite::helpers::in_memory_db;
547/// # use bip39::Mnemonic;
548/// # use bitcoin::Network;
549/// # let (db_path, _) = in_memory_db();
550/// let network = Network::Signet;
551/// let mnemonic = Mnemonic::generate(12)?;
552/// let cfg = Config {
553///   server_address: String::from("https://ark.signet.2nd.dev"),
554///   esplora_address: Some(String::from("https://esplora.signet.2nd.dev")),
555///   ..Default::default()
556/// };
557///
558/// // You can either use the included SQLite implementation or create your own.
559/// let persister = SqliteClient::open(db_path).await?;
560/// let db: Arc<dyn BarkPersister> = Arc::new(persister);
561///
562/// // Load or create an onchain wallet if needed
563/// let onchain_wallet = OnchainWallet::load_or_create(network, mnemonic.to_seed(""), db.clone()).await?;
564///
565/// // Create or open the Ark wallet
566/// let mut wallet = Wallet::create_with_onchain(
567/// 	&mnemonic,
568/// 	network,
569/// 	cfg.clone(),
570/// 	db,
571/// 	&onchain_wallet,
572/// 	false,
573/// ).await?;
574/// // let mut wallet = Wallet::create(&mnemonic, network, cfg.clone(), db.clone(), false).await?;
575/// // let mut wallet = Wallet::open(&mnemonic, db.clone(), cfg.clone()).await?;
576/// // let mut wallet = Wallet::open_with_onchain(
577/// //    &mnemonic, network, cfg.clone(), db.clone(), &onchain_wallet
578/// // ).await?;
579///
580/// // There are two main ways to update the wallet, the primary is to use one of the maintenance
581/// // commands which will sync everything, refresh VTXOs and reconcile pending lightning payments.
582/// wallet.maintenance().await?;
583/// wallet.maintenance_with_onchain(&mut onchain_wallet).await?;
584///
585/// // Alternatively, you can use the fine-grained sync commands to sync individual parts of the
586/// // wallet state and use `maintenance_refresh` where necessary to refresh VTXOs.
587/// wallet.sync().await?;
588/// wallet.sync_pending_lightning_send_vtxos().await?;
589/// wallet.register_all_confirmed_boards(&mut onchain_wallet).await?;
590/// wallet.sync_exits(&mut onchain_wallet).await?;
591/// wallet.maintenance_refresh().await?;
592///
593/// // Generate a new Ark address to receive funds via arkoor
594/// let addr = wallet.new_address().await?;
595///
596/// // Query balance and VTXOs
597/// let balance = wallet.balance()?;
598/// let vtxos = wallet.vtxos()?;
599///
600/// // Progress any unilateral exits, make sure to sync first
601/// wallet.exit.progress_exit(&mut onchain_wallet, None).await?;
602///
603/// # Ok(())
604/// # }
605/// ```
606pub struct Wallet {
607	/// The chain source the wallet is connected to
608	pub chain: Arc<ChainSource>,
609
610	/// Exit subsystem handling unilateral exits and on-chain reconciliation outside Ark rounds.
611	pub exit: RwLock<Exit>,
612
613	/// Allows easy creation of and management of wallet fund movements.
614	pub movements: Arc<MovementManager>,
615
616	/// Active runtime configuration for networking, fees, policies and thresholds.
617	config: Config,
618
619	/// Persistence backend for wallet state (keys metadata, VTXOs, movements, round state, etc.).
620	db: Arc<dyn BarkPersister>,
621
622	/// Deterministic seed material used to generate wallet keypairs.
623	seed: WalletSeed,
624
625	/// Optional live connection to an Ark server for round participation and synchronization.
626	server: parking_lot::RwLock<Option<ServerConnection>>,
627}
628
629impl Wallet {
630	/// Creates a [chain::ChainSource] instance to communicate with an onchain backend from the
631	/// given [Config].
632	pub fn chain_source(
633		config: &Config,
634	) -> anyhow::Result<ChainSourceSpec> {
635		if let Some(ref url) = config.esplora_address {
636			Ok(ChainSourceSpec::Esplora {
637				url: url.clone(),
638			})
639		} else if let Some(ref url) = config.bitcoind_address {
640			let auth = if let Some(ref c) = config.bitcoind_cookiefile {
641				bitcoin_ext::rpc::Auth::CookieFile(c.clone())
642			} else {
643				bitcoin_ext::rpc::Auth::UserPass(
644					config.bitcoind_user.clone().context("need bitcoind auth config")?,
645					config.bitcoind_pass.clone().context("need bitcoind auth config")?,
646				)
647			};
648			Ok(ChainSourceSpec::Bitcoind {
649				url: url.clone(),
650				auth,
651			})
652		} else {
653			bail!("Need to either provide esplora or bitcoind info");
654		}
655	}
656
657	/// Verifies that the bark [Wallet] can be used with the configured [chain::ChainSource].
658	/// More specifically, if the [chain::ChainSource] connects to Bitcoin Core it must be
659	/// a high enough version to support ephemeral anchors.
660	pub fn require_chainsource_version(&self) -> anyhow::Result<()> {
661		self.chain.require_version()
662	}
663
664	pub async fn network(&self) -> anyhow::Result<Network> {
665		Ok(self.properties().await?.network)
666	}
667
668	/// Derive and store the keypair directly after currently last revealed one,
669	/// together with its index.
670	pub async fn derive_store_next_keypair(&self) -> anyhow::Result<(Keypair, u32)> {
671		let last_revealed = self.db.get_last_vtxo_key_index().await?;
672
673		let index = last_revealed.map(|i| i + 1).unwrap_or(u32::MIN);
674		let keypair = self.seed.derive_vtxo_keypair(index);
675
676		self.db.store_vtxo_key(index, keypair.public_key()).await?;
677		Ok((keypair, index))
678	}
679
680	/// Retrieves a keypair based on the provided index and checks if the corresponding public key
681	/// exists in the [Vtxo] database.
682	///
683	/// # Arguments
684	///
685	/// * `index` - The index used to derive a keypair.
686	///
687	/// # Returns
688	///
689	/// * `Ok(Keypair)` - If the keypair is successfully derived and its public key exists in the
690	///   database.
691	/// * `Err(anyhow::Error)` - If the public key does not exist in the database or if an error
692	///   occurs during the database query.
693	pub async fn peak_keypair(&self, index: u32) -> anyhow::Result<Keypair> {
694		let keypair = self.seed.derive_vtxo_keypair(index);
695		if self.db.get_public_key_idx(&keypair.public_key()).await?.is_some() {
696			Ok(keypair)
697		} else {
698			bail!("VTXO key {} does not exist, please derive it first", index)
699		}
700	}
701
702
703	/// Retrieves the [Keypair] for a provided [PublicKey]
704	///
705	/// # Arguments
706	///
707	/// * `public_key` - The public key for which the keypair must be found
708	///
709	/// # Returns
710	/// * `Ok(Some(u32, Keypair))` - If the pubkey is found, the derivation-index and keypair are
711	///                              returned
712	/// * `Ok(None)` - If the pubkey cannot be found in the database
713	/// * `Err(anyhow::Error)` - If an error occurred related to the database query
714	pub async fn pubkey_keypair(&self, public_key: &PublicKey) -> anyhow::Result<Option<(u32, Keypair)>> {
715		if let Some(index) = self.db.get_public_key_idx(&public_key).await? {
716			Ok(Some((index, self.seed.derive_vtxo_keypair(index))))
717		} else {
718			Ok(None)
719		}
720	}
721
722	/// Retrieves the [Keypair] for a provided [Vtxo]
723	///
724	/// # Arguments
725	///
726	/// * `vtxo` - The vtxo for which the key must be found
727	///
728	/// # Returns
729	/// * `Ok(Some(Keypair))` - If the pubkey is found, the keypair is returned
730	/// * `Err(anyhow::Error)` - If the corresponding public key doesn't exist
731	///   in the database or a database error occurred.
732	pub async fn get_vtxo_key(&self, vtxo: impl VtxoRef) -> anyhow::Result<Keypair> {
733		let vtxo = match vtxo.vtxo() {
734			Some(v) => v,
735			None => &self.get_vtxo_by_id(vtxo.vtxo_id()).await?,
736		};
737		let pubkey = self.find_signable_clause(vtxo).await
738			.context("VTXO is not signable by wallet")?
739			.pubkey();
740		let idx = self.db.get_public_key_idx(&pubkey).await?
741			.context("VTXO key not found")?;
742		Ok(self.seed.derive_vtxo_keypair(idx))
743	}
744
745	/// Generate a new [ark::Address].
746	pub async fn new_address(&self) -> anyhow::Result<ark::Address> {
747		let srv = &self.require_server()?;
748		let network = self.properties().await?.network;
749		let (keypair, _) = self.derive_store_next_keypair().await?;
750
751		Ok(ark::Address::builder()
752			.testnet(network != bitcoin::Network::Bitcoin)
753			.server_pubkey(srv.ark_info().await?.server_pubkey)
754			.pubkey_policy(keypair.public_key())
755			.into_address().unwrap())
756	}
757
758	/// Peak for an [ark::Address] at the given key index.
759	///
760	/// May return an error if the address at the given index has not been derived yet.
761	pub async fn peak_address(&self, index: u32) -> anyhow::Result<ark::Address> {
762		let srv = &self.require_server()?;
763		let network = self.properties().await?.network;
764		let keypair = self.peak_keypair(index).await?;
765
766		Ok(ark::Address::builder()
767			.testnet(network != Network::Bitcoin)
768			.server_pubkey(srv.ark_info().await?.server_pubkey)
769			.pubkey_policy(keypair.public_key())
770			.into_address().unwrap())
771	}
772
773	/// Generate a new [ark::Address] and returns the index of the key used to create it.
774	///
775	/// This derives and stores the keypair directly after currently last revealed one.
776	pub async fn new_address_with_index(&self) -> anyhow::Result<(ark::Address, u32)> {
777		let srv = &self.require_server()?;
778		let network = self.properties().await?.network;
779		let (keypair, index) = self.derive_store_next_keypair().await?;
780		let pubkey = keypair.public_key();
781		let addr = ark::Address::builder()
782			.testnet(network != bitcoin::Network::Bitcoin)
783			.server_pubkey(srv.ark_info().await?.server_pubkey)
784			.pubkey_policy(pubkey)
785			.into_address()?;
786		Ok((addr, index))
787	}
788
789	/// Create a new wallet without an optional onchain backend. This will restrict features such as
790	/// boarding and unilateral exit.
791	///
792	/// The `force` flag will allow you to create the wallet even if a connection to the Ark server
793	/// cannot be established, it will not overwrite a wallet which has already been created.
794	pub async fn create(
795		mnemonic: &Mnemonic,
796		network: Network,
797		config: Config,
798		db: Arc<dyn BarkPersister>,
799		force: bool,
800	) -> anyhow::Result<Wallet> {
801		trace!("Config: {:?}", config);
802		if let Some(existing) = db.read_properties().await? {
803			trace!("Existing config: {:?}", existing);
804			bail!("cannot overwrite already existing config")
805		}
806
807		if !force {
808			if let Err(err) = ServerConnection::connect(&config.server_address, network).await {
809				bail!("Failed to connect to provided server (if you are sure use the --force flag): {}", err);
810			}
811		}
812
813		let wallet_fingerprint = WalletSeed::new(network, &mnemonic.to_seed("")).fingerprint();
814		let properties = WalletProperties {
815			network: network,
816			fingerprint: wallet_fingerprint,
817		};
818
819		// write the config to db
820		db.init_wallet(&properties).await.context("cannot init wallet in the database")?;
821		info!("Created wallet with fingerprint: {}", wallet_fingerprint);
822
823		// from then on we can open the wallet
824		let wallet = Wallet::open(&mnemonic, db, config).await.context("failed to open wallet")?;
825		wallet.require_chainsource_version()?;
826
827		Ok(wallet)
828	}
829
830	/// Create a new wallet with an onchain backend. This enables full Ark functionality. A default
831	/// implementation of an onchain wallet when the `onchain_bdk` feature is enabled. See
832	/// [onchain::OnchainWallet] for more details. Alternatively, implement [ExitUnilaterally] if
833	/// you have your own onchain wallet implementation.
834	///
835	/// The `force` flag will allow you to create the wallet even if a connection to the Ark server
836	/// cannot be established, it will not overwrite a wallet which has already been created.
837	pub async fn create_with_onchain(
838		mnemonic: &Mnemonic,
839		network: Network,
840		config: Config,
841		db: Arc<dyn BarkPersister>,
842		onchain: &dyn ExitUnilaterally,
843		force: bool,
844	) -> anyhow::Result<Wallet> {
845		let mut wallet = Wallet::create(mnemonic, network, config, db, force).await?;
846		wallet.exit.get_mut().load(onchain).await?;
847		Ok(wallet)
848	}
849
850	/// Loads the bark wallet from the given database ensuring the fingerprint remains consistent.
851	pub async fn open(
852		mnemonic: &Mnemonic,
853		db: Arc<dyn BarkPersister>,
854		config: Config,
855	) -> anyhow::Result<Wallet> {
856		let properties = db.read_properties().await?.context("Wallet is not initialised")?;
857
858		let seed = {
859			let seed = mnemonic.to_seed("");
860			WalletSeed::new(properties.network, &seed)
861		};
862
863		if properties.fingerprint != seed.fingerprint() {
864			bail!("incorrect mnemonic")
865		}
866
867		let chain_source = if let Some(ref url) = config.esplora_address {
868			ChainSourceSpec::Esplora {
869				url: url.clone(),
870			}
871		} else if let Some(ref url) = config.bitcoind_address {
872			let auth = if let Some(ref c) = config.bitcoind_cookiefile {
873				bitcoin_ext::rpc::Auth::CookieFile(c.clone())
874			} else {
875				bitcoin_ext::rpc::Auth::UserPass(
876					config.bitcoind_user.clone().context("need bitcoind auth config")?,
877					config.bitcoind_pass.clone().context("need bitcoind auth config")?,
878				)
879			};
880			ChainSourceSpec::Bitcoind { url: url.clone(), auth }
881		} else {
882			bail!("Need to either provide esplora or bitcoind info");
883		};
884
885		let chain_source_client = ChainSource::new(
886			chain_source, properties.network, config.fallback_fee_rate,
887		).await?;
888		let chain = Arc::new(chain_source_client);
889
890		let server = match ServerConnection::connect(
891			&config.server_address, properties.network,
892		).await {
893			Ok(s) => Some(s),
894			Err(e) => {
895				warn!("Ark server handshake failed: {}", e);
896				None
897			}
898		};
899		let server = parking_lot::RwLock::new(server);
900
901		let movements = Arc::new(MovementManager::new(db.clone()));
902		let exit = RwLock::new(Exit::new(db.clone(), chain.clone(), movements.clone()).await?);
903
904		Ok(Wallet { config, db, seed, exit, movements, server, chain })
905	}
906
907	/// Similar to [Wallet::open] however this also unilateral exits using the provided onchain
908	/// wallet.
909	pub async fn open_with_onchain(
910		mnemonic: &Mnemonic,
911		db: Arc<dyn BarkPersister>,
912		onchain: &dyn ExitUnilaterally,
913		cfg: Config,
914	) -> anyhow::Result<Wallet> {
915		let mut wallet = Wallet::open(mnemonic, db, cfg).await?;
916		wallet.exit.get_mut().load(onchain).await?;
917		Ok(wallet)
918	}
919
920	/// Returns the config used to create/load the bark [Wallet].
921	pub fn config(&self) -> &Config {
922		&self.config
923	}
924
925	/// Retrieves the [WalletProperties] of the current bark [Wallet].
926	pub async fn properties(&self) -> anyhow::Result<WalletProperties> {
927		let properties = self.db.read_properties().await?.context("Wallet is not initialised")?;
928		Ok(properties)
929	}
930
931	/// Returns the fingerprint of the wallet.
932	pub fn fingerprint(&self) -> Fingerprint {
933		self.seed.fingerprint()
934	}
935
936	fn require_server(&self) -> anyhow::Result<ServerConnection> {
937		self.server.read().clone()
938			.context("You should be connected to Ark server to perform this action")
939	}
940
941	pub async fn refresh_server(&self) -> anyhow::Result<()> {
942		let server = self.server.read().clone();
943
944		let srv = if let Some(srv) = server {
945			srv.check_connection().await?;
946			srv.ark_info().await?;
947			srv
948		} else {
949			let srv_address = &self.config.server_address;
950			let network = self.properties().await?.network;
951
952			ServerConnection::connect(srv_address, network).await?
953		};
954
955		let _ = self.server.write().insert(srv);
956
957		Ok(())
958	}
959
960	/// Return [ArkInfo] fetched on last handshake with the Ark server
961	pub async fn ark_info(&self) -> anyhow::Result<Option<ArkInfo>> {
962		let server = self.server.read().clone();
963		match server.as_ref() {
964			Some(srv) => Ok(Some(srv.ark_info().await?)),
965			_ => Ok(None),
966		}
967	}
968
969	/// Return the [Balance] of the wallet.
970	///
971	/// Make sure you sync before calling this method.
972	pub async fn balance(&self) -> anyhow::Result<Balance> {
973		let vtxos = self.vtxos().await?;
974
975		let spendable = {
976			let mut v = vtxos.iter().collect();
977			VtxoStateKind::Spendable.filter_vtxos(&mut v).await?;
978			v.into_iter().map(|v| v.amount()).sum::<Amount>()
979		};
980
981		let pending_lightning_send = self.pending_lightning_send_vtxos().await?.iter().map(|v| v.amount())
982			.sum::<Amount>();
983
984		let claimable_lightning_receive = self.claimable_lightning_receive_balance().await?;
985
986		let pending_board = self.pending_board_vtxos().await?.iter().map(|v| v.amount()).sum::<Amount>();
987
988		let pending_in_round = self.pending_round_input_vtxos().await?.iter().map(|v| v.amount()).sum();
989
990		let pending_exit = self.exit.try_read().ok().map(|e| e.pending_total());
991
992		Ok(Balance {
993			spendable,
994			pending_in_round,
995			pending_lightning_send,
996			claimable_lightning_receive,
997			pending_exit,
998			pending_board,
999		})
1000	}
1001
1002	/// Fetches [Vtxo]'s funding transaction and validates the VTXO against it.
1003	pub async fn validate_vtxo(&self, vtxo: &Vtxo) -> anyhow::Result<()> {
1004		let tx = self.chain.get_tx(&vtxo.chain_anchor().txid).await
1005			.context("could not fetch chain tx")?;
1006
1007		let tx = tx.with_context(|| {
1008			format!("vtxo chain anchor not found for vtxo: {}", vtxo.chain_anchor().txid)
1009		})?;
1010
1011		vtxo.validate(&tx)?;
1012
1013		Ok(())
1014	}
1015
1016	/// Retrieves the full state of a [Vtxo] for a given [VtxoId] if it exists in the database.
1017	pub async fn get_vtxo_by_id(&self, vtxo_id: VtxoId) -> anyhow::Result<WalletVtxo> {
1018		let vtxo = self.db.get_wallet_vtxo(vtxo_id).await
1019			.with_context(|| format!("Error when querying vtxo {} in database", vtxo_id))?
1020			.with_context(|| format!("The VTXO with id {} cannot be found", vtxo_id))?;
1021		Ok(vtxo)
1022	}
1023
1024	/// Fetches all movements ordered from newest to oldest.
1025	#[deprecated(since="0.1.0-beta.5", note = "Use Wallet::history instead")]
1026	pub async fn movements(&self) -> anyhow::Result<Vec<Movement>> {
1027		self.history().await
1028	}
1029
1030	/// Fetches all wallet fund movements ordered from newest to oldest.
1031	pub async fn history(&self) -> anyhow::Result<Vec<Movement>> {
1032		Ok(self.db.get_all_movements().await?)
1033	}
1034
1035	/// Returns all VTXOs from the database.
1036	pub async fn all_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1037		Ok(self.db.get_all_vtxos().await?)
1038	}
1039
1040	/// Returns all not spent vtxos
1041	pub async fn vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1042		Ok(self.db.get_vtxos_by_state(&VtxoStateKind::UNSPENT_STATES).await?)
1043	}
1044
1045	/// Returns all vtxos matching the provided predicate
1046	pub async fn vtxos_with(&self, filter: &impl FilterVtxos) -> anyhow::Result<Vec<WalletVtxo>> {
1047		let mut vtxos = self.vtxos().await?;
1048		filter.filter_vtxos(&mut vtxos).await.context("error filtering vtxos")?;
1049		Ok(vtxos)
1050	}
1051
1052	/// Returns all spendable vtxos
1053	pub async fn spendable_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1054		Ok(self.vtxos_with(&VtxoStateKind::Spendable).await?)
1055	}
1056
1057	/// Returns all spendable vtxos matching the provided predicate
1058	pub async fn spendable_vtxos_with(
1059		&self,
1060		filter: &impl FilterVtxos,
1061	) -> anyhow::Result<Vec<WalletVtxo>> {
1062		let mut vtxos = self.spendable_vtxos().await?;
1063		filter.filter_vtxos(&mut vtxos).await.context("error filtering vtxos")?;
1064		Ok(vtxos)
1065	}
1066
1067	pub async fn pending_boards(&self) -> anyhow::Result<Vec<PendingBoard>> {
1068		let boarding_vtxo_ids = self.db.get_all_pending_board_ids().await?;
1069		let mut boards = Vec::with_capacity(boarding_vtxo_ids.len());
1070		for vtxo_id in boarding_vtxo_ids {
1071			let board = self.db.get_pending_board_by_vtxo_id(vtxo_id).await?
1072				.expect("id just retrieved from db");
1073			boards.push(board);
1074		}
1075		Ok(boards)
1076	}
1077
1078	/// Queries the database for any VTXO that is an unregistered board. There is a lag time between
1079	/// when a board is created and when it becomes spendable.
1080	///
1081	/// See [ArkInfo::required_board_confirmations] and [Wallet::sync_pending_boards].
1082	pub async fn pending_board_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1083		let vtxo_ids = self.pending_boards().await?.into_iter()
1084			.flat_map(|b| b.vtxos.into_iter())
1085			.collect::<Vec<_>>();
1086
1087		let mut vtxos = Vec::with_capacity(vtxo_ids.len());
1088		for vtxo_id in vtxo_ids {
1089			let vtxo = self.get_vtxo_by_id(vtxo_id).await
1090				.expect("vtxo id just got retrieved from db");
1091			vtxos.push(vtxo);
1092		}
1093
1094		debug_assert!(vtxos.iter().all(|v| matches!(v.state.kind(), VtxoStateKind::Locked)),
1095			"all pending board vtxos should be locked"
1096		);
1097
1098		Ok(vtxos)
1099	}
1100
1101	/// Returns all VTXOs that are locked in a pending round
1102	///
1103	/// This excludes all input VTXOs for which the output VTXOs have already
1104	/// been created.
1105	pub async fn pending_round_input_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1106		let mut ret = Vec::new();
1107		for round in self.db.load_round_states().await? {
1108			let inputs = round.state.locked_pending_inputs();
1109			ret.reserve(inputs.len());
1110			for input in inputs {
1111				let v = self.get_vtxo_by_id(input.id()).await
1112					.context("unknown round input VTXO")?;
1113				ret.push(v);
1114			}
1115		}
1116		Ok(ret)
1117	}
1118
1119	/// Queries the database for any VTXO that is a pending lightning send.
1120	pub async fn pending_lightning_send_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1121		let vtxos = self.db.get_all_pending_lightning_send().await?.into_iter()
1122			.flat_map(|pending_lightning_send| pending_lightning_send.htlc_vtxos)
1123			.collect::<Vec<_>>();
1124
1125		Ok(vtxos)
1126	}
1127
1128	/// Returns all vtxos that will expire within `threshold` blocks
1129	pub async fn get_expiring_vtxos(
1130		&self,
1131		threshold: BlockHeight,
1132	) -> anyhow::Result<Vec<WalletVtxo>> {
1133		let expiry = self.chain.tip().await? + threshold;
1134		let filter = VtxoFilter::new(&self).expires_before(expiry);
1135		Ok(self.spendable_vtxos_with(&filter).await?)
1136	}
1137
1138	/// Attempts to register all pendings boards with the Ark server. A board transaction must have
1139	/// sufficient confirmations before it will be registered. For more details see
1140	/// [ArkInfo::required_board_confirmations].
1141	pub async fn sync_pending_boards(&self) -> anyhow::Result<()> {
1142		let ark_info = self.require_server()?.ark_info().await?;
1143		let current_height = self.chain.tip().await?;
1144		let unregistered_boards = self.pending_boards().await?;
1145		let mut registered_boards = 0;
1146
1147		if unregistered_boards.is_empty() {
1148			return Ok(());
1149		}
1150
1151		trace!("Attempting registration of sufficiently confirmed boards");
1152
1153		for board in unregistered_boards {
1154			let [vtxo_id] = board.vtxos.try_into()
1155				.map_err(|_| anyhow!("multiple board vtxos is not supported yet"))?;
1156
1157			let vtxo = self.get_vtxo_by_id(vtxo_id).await?;
1158
1159			let anchor = vtxo.chain_anchor();
1160			let confs = match self.chain.tx_status(anchor.txid).await {
1161				Ok(TxStatus::Confirmed(block_ref)) => Some(current_height - (block_ref.height - 1)),
1162				Ok(TxStatus::Mempool) => Some(0),
1163				Ok(TxStatus::NotFound) => None,
1164				Err(_) => None,
1165			};
1166
1167			if let Some(confs) = confs {
1168				if confs >= ark_info.required_board_confirmations as BlockHeight {
1169					if let Err(e) = self.register_board(vtxo.id()).await {
1170						warn!("Failed to register board {}: {}", vtxo.id(), e);
1171					} else {
1172						info!("Registered board {}", vtxo.id());
1173						registered_boards += 1;
1174					}
1175				}
1176
1177				continue;
1178			}
1179
1180			if vtxo.expiry_height() < current_height + ark_info.required_board_confirmations as BlockHeight {
1181				warn!("VTXO {} expired before its board was confirmed, removing board", vtxo.id());
1182				self.movements.finish_movement(board.movement_id, MovementStatus::Failed).await?;
1183				self.mark_vtxos_as_spent(&[vtxo]).await?;
1184				self.db.remove_pending_board(&vtxo_id).await?;
1185			}
1186		};
1187
1188		if registered_boards > 0 {
1189			info!("Registered {registered_boards} sufficiently confirmed boards");
1190		}
1191		Ok(())
1192	}
1193
1194	/// Performs maintenance tasks on the offchain wallet.
1195	///
1196	/// This can take a long period of time due to syncing rounds, arkoors, checking pending
1197	/// payments, progressing pending rounds, and refreshing VTXOs if necessary.
1198	pub async fn maintenance(&self) -> anyhow::Result<()> {
1199		info!("Starting wallet maintenance");
1200		self.sync().await;
1201		self.progress_pending_rounds(None).await?;
1202		self.maintenance_refresh().await?;
1203		Ok(())
1204	}
1205
1206	/// Performs maintenance tasks on the onchain and offchain wallet.
1207	///
1208	/// This can take a long period of time due to syncing the onchain wallet, registering boards,
1209	/// syncing rounds, arkoors, and the exit system, checking pending lightning payments and
1210	/// refreshing VTXOs if necessary.
1211	pub async fn maintenance_with_onchain<W: PreparePsbt + SignPsbt + ExitUnilaterally>(
1212		&self,
1213		onchain: &mut W,
1214	) -> anyhow::Result<()> {
1215		info!("Starting wallet maintenance with onchain wallet");
1216		self.sync().await;
1217		self.maintenance_refresh().await?;
1218
1219		// NB: order matters here, after syncing lightning, we might have new exits to start
1220		self.sync_exits(onchain).await?;
1221
1222		Ok(())
1223	}
1224
1225	/// Checks VTXOs that are due to be refreshed, and schedules a refresh if any
1226	///
1227	/// This will include any VTXOs within the expiry threshold
1228	/// ([Config::vtxo_refresh_expiry_threshold]) or those which
1229	/// are uneconomical to exit due to onchain network conditions.
1230	///
1231	/// Returns a [RoundStateId] if a refresh is scheduled.
1232	pub async fn maybe_schedule_maintenance_refresh(&self) -> anyhow::Result<Option<RoundStateId>> {
1233		let vtxos = self.get_vtxos_to_refresh().await?.into_iter()
1234			.map(|v| v.id())
1235			.collect::<Vec<_>>();
1236		if vtxos.len() == 0 {
1237			return Ok(None);
1238		}
1239
1240		info!("Scheduling maintenance refresh");
1241		let mut participation = match self.build_refresh_participation(vtxos).await? {
1242			Some(participation) => participation,
1243			None => return Ok(None),
1244		};
1245
1246		if let Err(e) = self.add_should_refresh_vtxos(&mut participation).await {
1247			warn!("Error trying to add additional VTXOs that should be refreshed: {:#}", e);
1248		}
1249
1250		let state = self.join_next_round(participation, Some(RoundMovement::Refresh)).await?;
1251		Ok(Some(state.id))
1252	}
1253
1254	/// Performs a refresh of all VTXOs that are due to be refreshed, if any. This will include any
1255	/// VTXOs within the expiry threshold ([Config::vtxo_refresh_expiry_threshold]) or those which
1256	/// are uneconomical to exit due to onchain network conditions.
1257	///
1258	/// Returns a [RoundStatus] if a refresh occurs.
1259	pub async fn maintenance_refresh(&self) -> anyhow::Result<Option<RoundStatus>> {
1260		let vtxos = self.get_vtxos_to_refresh().await?.into_iter()
1261			.map(|v| v.id())
1262			.collect::<Vec<_>>();
1263		if vtxos.len() == 0 {
1264			return Ok(None);
1265		}
1266
1267		info!("Performing maintenance refresh");
1268		self.refresh_vtxos(vtxos).await
1269	}
1270
1271	/// Sync offchain wallet and update onchain fees. This is a much more lightweight alternative
1272	/// to [Wallet::maintenance] as it will not refresh VTXOs or sync the onchain wallet.
1273	///
1274	/// Notes:
1275	/// - The exit system will not be synced as doing so requires the onchain wallet.
1276	pub async fn sync(&self) {
1277		tokio::join!(
1278			async {
1279				// NB: order matters here, if syncing call fails,
1280				// we still want to update the fee rates
1281				if let Err(e) = self.chain.update_fee_rates(self.config.fallback_fee_rate).await {
1282					warn!("Error updating fee rates: {:#}", e);
1283				}
1284			},
1285			async {
1286				if let Err(e) = self.sync_oors().await {
1287					warn!("Error in arkoor sync: {:#}", e);
1288				}
1289			},
1290			async {
1291				if let Err(e) = self.sync_pending_rounds().await {
1292					warn!("Error while trying to progress rounds awaiting confirmations: {:#}", e);
1293				}
1294			},
1295			async {
1296				if let Err(e) = self.sync_pending_lightning_send_vtxos().await {
1297					warn!("Error syncing pending lightning payments: {:#}", e);
1298				}
1299			},
1300			async {
1301				if let Err(e) = self.try_claim_all_lightning_receives(false).await {
1302					warn!("Error claiming pending lightning receives: {:#}", e);
1303				}
1304			},
1305			async {
1306				if let Err(e) = self.sync_pending_boards().await {
1307					warn!("Error syncing pending boards: {:#}", e);
1308				}
1309			}
1310		);
1311	}
1312
1313	/// Sync the transaction status of unilateral exits
1314	///
1315	/// This will not progress the unilateral exits in any way, it will merely check the
1316	/// transaction status of each transaction as well as check whether any exits have become
1317	/// claimable or have been claimed.
1318	pub async fn sync_exits(
1319		&self,
1320		onchain: &mut dyn ExitUnilaterally,
1321	) -> anyhow::Result<()> {
1322		self.exit.write().await.sync(&self, onchain).await?;
1323		Ok(())
1324	}
1325
1326	pub async fn pending_lightning_sends(&self) -> anyhow::Result<Vec<LightningSend>> {
1327		Ok(self.db.get_all_pending_lightning_send().await?)
1328	}
1329
1330	/// Syncs pending lightning payments, verifying whether the payment status has changed and
1331	/// creating a revocation VTXO if necessary.
1332	pub async fn sync_pending_lightning_send_vtxos(&self) -> anyhow::Result<()> {
1333		let pending_payments = self.pending_lightning_sends().await?;
1334
1335		if pending_payments.is_empty() {
1336			return Ok(());
1337		}
1338
1339		info!("Syncing {} pending lightning sends", pending_payments.len());
1340
1341		for payment in pending_payments {
1342			let payment_hash = payment.invoice.payment_hash();
1343			self.check_lightning_payment(payment_hash, false).await?;
1344		}
1345
1346		Ok(())
1347	}
1348
1349	/// Drop a specific [Vtxo] from the database. This is destructive and will result in a loss of
1350	/// funds.
1351	pub async fn dangerous_drop_vtxo(&self, vtxo_id: VtxoId) -> anyhow::Result<()> {
1352		warn!("Drop vtxo {} from the database", vtxo_id);
1353		self.db.remove_vtxo(vtxo_id).await?;
1354		Ok(())
1355	}
1356
1357	/// Drop all VTXOs from the database. This is destructive and will result in a loss of funds.
1358	//TODO(stevenroose) improve the way we expose dangerous methods
1359	pub async fn dangerous_drop_all_vtxos(&self) -> anyhow::Result<()> {
1360		warn!("Dropping all vtxos from the db...");
1361		for vtxo in self.vtxos().await? {
1362			self.db.remove_vtxo(vtxo.id()).await?;
1363		}
1364
1365		self.exit.write().await.dangerous_clear_exit().await?;
1366		Ok(())
1367	}
1368
1369	/// Board a [Vtxo] with the given amount.
1370	///
1371	/// NB we will spend a little more onchain to cover fees.
1372	pub async fn board_amount(
1373		&self,
1374		onchain: &mut dyn onchain::Board,
1375		amount: Amount,
1376	) -> anyhow::Result<PendingBoard> {
1377		let (user_keypair, _) = self.derive_store_next_keypair().await?;
1378		self.board(onchain, Some(amount), user_keypair).await
1379	}
1380
1381	/// Board a [Vtxo] with all the funds in your onchain wallet.
1382	pub async fn board_all(
1383		&self,
1384		onchain: &mut dyn onchain::Board,
1385	) -> anyhow::Result<PendingBoard> {
1386		let (user_keypair, _) = self.derive_store_next_keypair().await?;
1387		self.board(onchain, None, user_keypair).await
1388	}
1389
1390	async fn board(
1391		&self,
1392		wallet: &mut dyn onchain::Board,
1393		amount: Option<Amount>,
1394		user_keypair: Keypair,
1395	) -> anyhow::Result<PendingBoard> {
1396		let mut srv = self.require_server()?;
1397		let ark_info = srv.ark_info().await?;
1398
1399		let properties = self.db.read_properties().await?.context("Missing config")?;
1400		let current_height = self.chain.tip().await?;
1401
1402		let expiry_height = current_height + ark_info.vtxo_expiry_delta as BlockHeight;
1403		let builder = BoardBuilder::new(
1404			user_keypair.public_key(),
1405			expiry_height,
1406			ark_info.server_pubkey,
1407			ark_info.vtxo_exit_delta,
1408		);
1409
1410		let addr = bitcoin::Address::from_script(
1411			&builder.funding_script_pubkey(),
1412			properties.network,
1413		)?;
1414
1415		// We create the board tx template, but don't sign it yet.
1416		let fee_rate = self.chain.fee_rates().await.regular;
1417		let (board_psbt, amount) = if let Some(amount) = amount {
1418			let psbt = wallet.prepare_tx(&[(addr, amount)], fee_rate)?;
1419			(psbt, amount)
1420		} else {
1421			let psbt = wallet.prepare_drain_tx(addr, fee_rate)?;
1422			assert_eq!(psbt.unsigned_tx.output.len(), 1);
1423			let amount = psbt.unsigned_tx.output[0].value;
1424			(psbt, amount)
1425		};
1426
1427		ensure!(amount >= ark_info.min_board_amount,
1428			"board amount of {amount} is less than minimum board amount required by server ({})",
1429			ark_info.min_board_amount,
1430		);
1431
1432		let utxo = OutPoint::new(board_psbt.unsigned_tx.compute_txid(), BOARD_FUNDING_TX_VTXO_VOUT);
1433		let builder = builder
1434			.set_funding_details(amount, utxo)
1435			.generate_user_nonces();
1436
1437		let cosign_resp = srv.client.request_board_cosign(protos::BoardCosignRequest {
1438			amount: amount.to_sat(),
1439			utxo: bitcoin::consensus::serialize(&utxo), //TODO(stevenroose) change to own
1440			expiry_height,
1441			user_pubkey: user_keypair.public_key().serialize().to_vec(),
1442			pub_nonce: builder.user_pub_nonce().serialize().to_vec(),
1443		}).await.context("error requesting board cosign")?
1444			.into_inner().try_into().context("invalid cosign response from server")?;
1445
1446		ensure!(builder.verify_cosign_response(&cosign_resp),
1447			"invalid board cosignature received from server",
1448		);
1449
1450		// Store vtxo first before we actually make the on-chain tx.
1451		let vtxo = builder.build_vtxo(&cosign_resp, &user_keypair)?;
1452
1453		let onchain_fee = board_psbt.fee()?;
1454		let movement_id = self.movements.new_movement_with_update(
1455			Subsystem::BOARD,
1456			BoardMovement::Board.to_string(),
1457			MovementUpdate::new()
1458				.produced_vtxo(&vtxo)
1459				.intended_and_effective_balance(vtxo.amount().to_signed()?)
1460				.metadata(BoardMovement::metadata(utxo, onchain_fee)),
1461		).await?;
1462		self.store_locked_vtxos([&vtxo], Some(movement_id)).await?;
1463
1464		let tx = wallet.finish_tx(board_psbt).await?;
1465		self.db.store_pending_board(&vtxo, &tx, movement_id).await?;
1466
1467		trace!("Broadcasting board tx: {}", bitcoin::consensus::encode::serialize_hex(&tx));
1468		self.chain.broadcast_tx(&tx).await?;
1469
1470		info!("Board broadcasted");
1471		Ok(self.db.get_pending_board_by_vtxo_id(vtxo.id()).await?.expect("board should be stored"))
1472	}
1473
1474	/// Registers a board to the Ark server
1475	async fn register_board(&self, vtxo: impl VtxoRef) -> anyhow::Result<()> {
1476		trace!("Attempting to register board {} to server", vtxo.vtxo_id());
1477		let mut srv = self.require_server()?;
1478
1479		// Get the vtxo and funding transaction from the database
1480		let vtxo = match vtxo.vtxo() {
1481			Some(v) => v,
1482			None => {
1483				&self.db.get_wallet_vtxo(vtxo.vtxo_id()).await?
1484					.with_context(|| format!("VTXO doesn't exist: {}", vtxo.vtxo_id()))?
1485			},
1486		};
1487
1488		// Register the vtxo with the server
1489		srv.client.register_board_vtxo(protos::BoardVtxoRequest {
1490			board_vtxo: vtxo.serialize(),
1491		}).await.context("error registering board with the Ark server")?;
1492
1493		// Remember that we have stored the vtxo
1494		// No need to complain if the vtxo is already registered
1495		self.db.update_vtxo_state_checked(
1496			vtxo.id(), VtxoState::Spendable, &VtxoStateKind::UNSPENT_STATES,
1497		).await?;
1498
1499		let board = self.db.get_pending_board_by_vtxo_id(vtxo.id()).await?
1500			.context("pending board not found")?;
1501
1502		self.movements.finish_movement(board.movement_id, MovementStatus::Successful).await?;
1503		self.db.remove_pending_board(&vtxo.id()).await?;
1504
1505		Ok(())
1506	}
1507
1508	/// Checks if the provided VTXO has some counterparty risk in the current wallet
1509	///
1510	/// An arkoor vtxo is considered to have some counterparty risk
1511	/// if it is (directly or not) based on round VTXOs that aren't owned by the wallet
1512	async fn has_counterparty_risk(&self, vtxo: &Vtxo) -> anyhow::Result<bool> {
1513		for past_pk in vtxo.past_arkoor_pubkeys() {
1514			if !self.db.get_public_key_idx(&past_pk).await?.is_some() {
1515				return Ok(true);
1516			}
1517		}
1518
1519		let my_clause = self.find_signable_clause(vtxo).await;
1520		Ok(!my_clause.is_some())
1521	}
1522
1523	pub async fn sync_oors(&self) -> anyhow::Result<()> {
1524		let last_pk_index = self.db.get_last_vtxo_key_index().await?.unwrap_or_default();
1525		let pubkeys = (0..=last_pk_index).map(|idx| {
1526			self.seed.derive_vtxo_keypair(idx).public_key()
1527		}).collect::<Vec<_>>();
1528
1529		self.sync_arkoor_for_pubkeys(&pubkeys).await?;
1530
1531		Ok(())
1532	}
1533
1534	/// Sync with the Ark server and look for out-of-round received VTXOs by public key.
1535	async fn sync_arkoor_for_pubkeys(
1536		&self,
1537		public_keys: &[PublicKey],
1538	) -> anyhow::Result<()> {
1539		let mut srv = self.require_server()?;
1540
1541		for pubkeys in public_keys.chunks(rpc::MAX_NB_MAILBOX_PUBKEYS) {
1542			// Then sync OOR vtxos.
1543			debug!("Emptying OOR mailbox at Ark server...");
1544			let req = protos::ArkoorVtxosRequest {
1545				pubkeys: pubkeys.iter().map(|pk| pk.serialize().to_vec()).collect(),
1546			};
1547
1548			#[allow(deprecated)]
1549			let packages = srv.client.empty_arkoor_mailbox(req).await
1550				.context("error fetching oors")?.into_inner().packages;
1551			debug!("Ark server has {} arkoor packages for us", packages.len());
1552
1553			for package in packages {
1554				let mut vtxos = Vec::with_capacity(package.vtxos.len());
1555				for vtxo in package.vtxos {
1556					let vtxo = match Vtxo::deserialize(&vtxo) {
1557						Ok(vtxo) => vtxo,
1558						Err(e) => {
1559							warn!("Invalid vtxo from Ark server: {}", e);
1560							continue;
1561						}
1562					};
1563
1564					if let Err(e) = self.validate_vtxo(&vtxo).await {
1565						error!("Received invalid arkoor VTXO from server: {}", e);
1566						continue;
1567					}
1568
1569					match self.db.has_spent_vtxo(vtxo.id()).await {
1570						Ok(spent) if spent => {
1571							debug!("Not adding OOR vtxo {} because it is considered spent", vtxo.id());
1572							continue;
1573						},
1574						_ => {}
1575					}
1576
1577					if let Ok(Some(_)) = self.db.get_wallet_vtxo(vtxo.id()).await {
1578						debug!("Not adding OOR vtxo {} because it already exists", vtxo.id());
1579						continue;
1580					}
1581
1582					vtxos.push(vtxo);
1583				}
1584
1585				self.store_spendable_vtxos(&vtxos).await?;
1586				self.movements.new_finished_movement(
1587					Subsystem::ARKOOR,
1588					ArkoorMovement::Receive.to_string(),
1589					MovementStatus::Successful,
1590					MovementUpdate::new()
1591						.produced_vtxos(&vtxos)
1592						.intended_and_effective_balance(
1593							vtxos
1594							.iter()
1595							.map(|vtxo| vtxo.amount()).sum::<Amount>()
1596							.to_signed()?,
1597						),
1598				).await?;
1599			}
1600		}
1601
1602		Ok(())
1603	}
1604
1605	/// If there are any VTXOs that match the "should-refresh" condition with
1606	/// a total value over the  p2tr dust limit, they are added to the round
1607	/// participation and an additional output is also created.
1608	async fn add_should_refresh_vtxos(
1609		&self,
1610		participation: &mut RoundParticipation,
1611	) -> anyhow::Result<()> {
1612		let excluded_ids = participation.inputs.iter().map(|v| v.vtxo_id())
1613			.collect::<HashSet<_>>();
1614
1615		let should_refresh_vtxos = self.get_vtxos_to_refresh().await?.into_iter()
1616			.filter(|v| !excluded_ids.contains(&v.id()))
1617			.map(|v| v.vtxo).collect::<Vec<_>>();
1618
1619
1620		let total_amount = should_refresh_vtxos.iter().map(|v| v.amount()).sum::<Amount>();
1621
1622		if total_amount > P2TR_DUST {
1623			let (user_keypair, _) = self.derive_store_next_keypair().await?;
1624			let req = VtxoRequest {
1625				policy: VtxoPolicy::new_pubkey(user_keypair.public_key()),
1626				amount: total_amount,
1627			};
1628
1629			participation.inputs.extend(should_refresh_vtxos);
1630			participation.outputs.push(req);
1631		}
1632
1633		Ok(())
1634	}
1635
1636	pub async fn build_refresh_participation<V: VtxoRef>(
1637		&self,
1638		vtxos: impl IntoIterator<Item = V>,
1639	) -> anyhow::Result<Option<RoundParticipation>> {
1640		let vtxos = {
1641			let mut ret = HashMap::new();
1642			for v in vtxos {
1643				let id = v.vtxo_id();
1644				let vtxo = self.get_vtxo_by_id(id).await
1645					.with_context(|| format!("vtxo with id {} not found", id))?;
1646				if !ret.insert(id, vtxo).is_none() {
1647					bail!("duplicate VTXO id: {}", id);
1648				}
1649			}
1650			ret
1651		};
1652
1653		if vtxos.is_empty() {
1654			info!("Skipping refresh since no VTXOs are provided.");
1655			return Ok(None);
1656		}
1657
1658		let total_amount = vtxos.values().map(|v| v.vtxo.amount()).sum();
1659
1660		info!("Refreshing {} VTXOs (total amount = {}).", vtxos.len(), total_amount);
1661
1662		let (user_keypair, _) = self.derive_store_next_keypair().await?;
1663		let req = VtxoRequest {
1664			policy: VtxoPolicy::Pubkey(PubkeyVtxoPolicy { user_pubkey: user_keypair.public_key() }),
1665			amount: total_amount,
1666		};
1667
1668		Ok(Some(RoundParticipation {
1669			inputs: vtxos.into_values().map(|v| v.vtxo).collect(),
1670			outputs: vec![req],
1671		}))
1672	}
1673
1674	/// This will refresh all provided VTXOs. Note that attempting to refresh a board VTXO which
1675	/// has not yet confirmed will result in an error.
1676	///
1677	/// Returns the [RoundStatus] of the round if a successful refresh occurred.
1678	/// It will return [None] if no [Vtxo] needed to be refreshed.
1679	pub async fn refresh_vtxos<V: VtxoRef>(
1680		&self,
1681		vtxos: impl IntoIterator<Item = V>,
1682	) -> anyhow::Result<Option<RoundStatus>> {
1683		let mut participation = match self.build_refresh_participation(vtxos).await? {
1684			Some(participation) => participation,
1685			None => return Ok(None),
1686		};
1687
1688		if let Err(e) = self.add_should_refresh_vtxos(&mut participation).await {
1689			warn!("Error trying to add additional VTXOs that should be refreshed: {:#}", e);
1690		}
1691
1692		Ok(Some(self.participate_round(participation, Some(RoundMovement::Refresh)).await?))
1693	}
1694
1695	/// This will find all VTXOs that meets must-refresh criteria.
1696	/// Then, if there are some VTXOs to refresh, it will
1697	/// also add those that meet should-refresh criteria.
1698	pub async fn get_vtxos_to_refresh(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1699		let tip = self.chain.tip().await?;
1700		let fee_rate = self.chain.fee_rates().await.fast;
1701
1702		// Check if there is any VTXO that we must refresh
1703		let must_refresh_vtxos = self.spendable_vtxos_with(
1704			&RefreshStrategy::must_refresh(self, tip, fee_rate),
1705		).await?;
1706		if must_refresh_vtxos.is_empty() {
1707			return Ok(vec![]);
1708		} else {
1709			// If we need to do a refresh, we take all the should_refresh vtxo's as well
1710			// This helps us to aggregate some VTXOs
1711			let should_refresh_vtxos = self.spendable_vtxos_with(
1712				&RefreshStrategy::should_refresh(self, tip, fee_rate),
1713			).await?;
1714			Ok(should_refresh_vtxos)
1715		}
1716	}
1717
1718	/// Returns the block height at which the first VTXO will expire
1719	pub async fn get_first_expiring_vtxo_blockheight(
1720		&self,
1721	) -> anyhow::Result<Option<BlockHeight>> {
1722		Ok(self.spendable_vtxos().await?.iter().map(|v| v.expiry_height()).min())
1723	}
1724
1725	/// Returns the next block height at which we have a VTXO that we
1726	/// want to refresh
1727	pub async fn get_next_required_refresh_blockheight(
1728		&self,
1729	) -> anyhow::Result<Option<BlockHeight>> {
1730		let first_expiry = self.get_first_expiring_vtxo_blockheight().await?;
1731		Ok(first_expiry.map(|h| h.saturating_sub(self.config.vtxo_refresh_expiry_threshold)))
1732	}
1733
1734	/// Select several VTXOs to cover the provided amount
1735	///
1736	/// VTXOs are selected soonest-expiring-first.
1737	///
1738	/// Returns an error if amount cannot be reached.
1739	async fn select_vtxos_to_cover(
1740		&self,
1741		amount: Amount,
1742	) -> anyhow::Result<Vec<WalletVtxo>> {
1743		let mut vtxos = self.spendable_vtxos().await?;
1744		vtxos.sort_by_key(|v| v.expiry_height());
1745
1746		// Iterate over VTXOs until the required amount is reached
1747		let mut result = Vec::new();
1748		let mut total_amount = Amount::ZERO;
1749		for input in vtxos {
1750			total_amount += input.amount();
1751			result.push(input);
1752
1753			if total_amount >= amount {
1754				return Ok(result)
1755			}
1756		}
1757
1758		bail!("Insufficient money available. Needed {} but {} is available",
1759			amount, total_amount,
1760		);
1761	}
1762
1763	/// Fetches all pending lightning receives ordered from newest to oldest.
1764	pub async fn pending_lightning_receives(&self) -> anyhow::Result<Vec<LightningReceive>> {
1765		Ok(self.db.get_all_pending_lightning_receives().await?)
1766	}
1767
1768	pub async fn claimable_lightning_receive_balance(&self) -> anyhow::Result<Amount> {
1769		let receives = self.pending_lightning_receives().await?;
1770
1771		let mut total = Amount::ZERO;
1772		for receive in receives {
1773			if let Some(htlc_vtxos) = receive.htlc_vtxos {
1774				total += htlc_vtxos.iter().map(|v| v.amount()).sum::<Amount>();
1775			}
1776		}
1777
1778		Ok(total)
1779	}
1780
1781	/// Starts a daemon for the wallet.
1782	///
1783	/// Note:
1784	/// - This function doesn't check if a daemon is already running,
1785	/// so it's possible to start multiple daemons by mistake.
1786	pub async fn run_daemon(
1787		self: &Arc<Self>,
1788		onchain: Arc<RwLock<dyn DaemonizableOnchainWallet>>,
1789	) -> anyhow::Result<DaemonHandle> {
1790		// NB currently can't error but it's a pretty common method and quite likely that error
1791		// cases will be introduces later
1792		Ok(crate::daemon::start_daemon(self.clone(), onchain))
1793	}
1794}