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 [onchain::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()?;
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().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::selection::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)?
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 serde;
290
291pub mod daemon;
292pub mod error;
293pub mod exit;
294pub mod lightning_utils;
295pub mod movement;
296pub mod onchain;
297pub mod persist;
298pub mod round;
299pub mod subsystem;
300pub mod vtxo;
301
302pub use self::config::{BarkNetwork, Config};
303pub use self::persist::sqlite::SqliteClient;
304pub use self::vtxo::state::WalletVtxo;
305
306mod config;
307mod lnurl;
308mod psbtext;
309
310use std::collections::{HashMap, HashSet};
311
312use std::convert::TryFrom;
313use std::fmt;
314use std::str::FromStr;
315use std::sync::Arc;
316
317use anyhow::{bail, Context};
318use bip39::Mnemonic;
319use bitcoin::{Amount, Network, OutPoint, ScriptBuf, SignedAmount};
320use bitcoin::bip32::{self, Fingerprint};
321use bitcoin::hex::DisplayHex;
322use bitcoin::secp256k1::{self, Keypair, PublicKey};
323use futures::StreamExt;
324use lightning_invoice::Bolt11Invoice;
325use lightning::util::ser::Writeable;
326use lnurllib::lightning_address::LightningAddress;
327use log::{trace, debug, info, warn, error};
328use tokio::sync::RwLock;
329use tokio_util::sync::CancellationToken;
330
331use ark::{ArkInfo, OffboardRequest, ProtocolEncoding, Vtxo, VtxoId, VtxoPolicy, VtxoRequest};
332use ark::address::VtxoDelivery;
333use ark::arkoor::ArkoorPackageBuilder;
334use ark::board::{BoardBuilder, BOARD_FUNDING_TX_VTXO_VOUT};
335use ark::lightning::{Bolt12Invoice, Bolt12InvoiceExt, Invoice, LightningReceiveChallenge, Offer, PaymentHash, Preimage};
336use ark::musig;
337use ark::rounds::RoundId;
338use ark::vtxo::{VtxoRef, PubkeyVtxoPolicy, VtxoPolicyKind};
339use bitcoin_ext::{AmountExt, BlockDelta, BlockHeight, P2TR_DUST, TxStatus};
340use server_rpc::protos::prepare_lightning_receive_claim_request::LightningReceiveAntiDos;
341use server_rpc::{self as rpc, protos, ServerConnection};
342
343use crate::daemon::Daemon;
344use crate::exit::Exit;
345use crate::movement::{Movement, MovementDestination, MovementStatus};
346use crate::movement::manager::{MovementGuard, MovementManager};
347use crate::movement::update::MovementUpdate;
348use crate::onchain::{ChainSource, PreparePsbt, ExitUnilaterally, Utxo, SignPsbt};
349use crate::persist::BarkPersister;
350use crate::persist::models::{PendingLightningSend, LightningReceive};
351use crate::round::{RoundParticipation, RoundStatus};
352use crate::subsystem::{
353	ArkoorMovement, BarkSubsystem, BoardMovement, LightningMovement, LightningReceiveMovement,
354	LightningSendMovement, RoundMovement, SubsystemId,
355};
356use crate::vtxo::selection::{FilterVtxos, VtxoFilter, RefreshStrategy};
357use crate::vtxo::state::{VtxoState, VtxoStateKind, UNSPENT_STATES};
358
359const ARK_PURPOSE_INDEX: u32 = 350;
360
361/// Leniency delta to allow claim when blocks were mined between htlc
362/// receive and claim preparation
363const LIGHTNING_PREPARE_CLAIM_DELTA: BlockDelta = 2;
364
365lazy_static::lazy_static! {
366	/// Global secp context.
367	static ref SECP: secp256k1::Secp256k1<secp256k1::All> = secp256k1::Secp256k1::new();
368}
369
370/// The detailled balance of a Lightning receive.
371#[derive(Debug, Clone)]
372pub struct LightningReceiveBalance {
373	/// Sum of all pending lightning invoices
374	pub total: Amount,
375	/// Sum of all invoices for which we received the HTLC VTXOs
376	pub claimable: Amount,
377}
378
379/// The different balances of a Bark wallet.
380#[derive(Debug, Clone)]
381pub struct Balance {
382	/// Coins that are spendable in the Ark, either in-round or out-of-round.
383	pub spendable: Amount,
384	/// Coins that are in the process of being sent over Lightning.
385	pub pending_lightning_send: Amount,
386	/// Coins that are in the process of being received over Lightning.
387	pub pending_lightning_receive: LightningReceiveBalance,
388	/// Coins locked in a round.
389	pub pending_in_round: Amount,
390	/// Coins that are in the process of unilaterally exiting the Ark.
391	/// None if exit subsystem was unavailable
392	pub pending_exit: Option<Amount>,
393	/// Coins that are pending sufficient confirmations from board transactions.
394	pub pending_board: Amount,
395}
396
397struct ArkoorCreateResult {
398	input: Vec<Vtxo>,
399	created: Vec<Vtxo>,
400	change: Option<Vtxo>,
401}
402
403impl ArkoorCreateResult {
404	pub fn to_movement_update(&self) -> anyhow::Result<MovementUpdate> {
405		Ok(MovementUpdate::new()
406			.consumed_vtxos(self.input.iter())
407			.produced_vtxo_if_some(self.change.as_ref())
408		)
409	}
410}
411
412pub struct UtxoInfo {
413	pub outpoint: OutPoint,
414	pub amount: Amount,
415	pub confirmation_height: Option<u32>,
416}
417
418impl From<Utxo> for UtxoInfo {
419	fn from(value: Utxo) -> Self {
420		match value {
421			Utxo::Local(o) => UtxoInfo {
422				outpoint: o.outpoint,
423				amount: o.amount,
424				confirmation_height: o.confirmation_height,
425			},
426			Utxo::Exit(e) => UtxoInfo {
427				outpoint: e.vtxo.point(),
428				amount: e.vtxo.amount(),
429				confirmation_height: Some(e.height),
430			},
431		}
432	}
433}
434
435/// Describes a completed transition of funds from onchain to offchain.
436#[derive(Debug, Clone, PartialEq, Eq, Hash)]
437pub struct Board {
438	/// The [bitcoin::Txid] of the funding-transaction.
439	/// This is the transaction that has to be confirmed
440	/// onchain for the board to succeed.
441	pub funding_txid: bitcoin::Txid,
442	/// The info for each [ark::Vtxo] that was created
443	/// in this board.
444	///
445	/// Currently, this is always a vector of length 1
446	pub vtxos: Vec<Vtxo>,
447}
448
449/// Describes a completed transition of funds from offchain to onchain collaboratively with the
450/// Ark server.
451#[derive(Debug, Clone, PartialEq, Eq, Hash)]
452pub struct Offboard {
453	/// The [RoundId] of the round in which the offboard occurred
454	pub round: RoundId,
455}
456
457/// Represents an offchain balance structure consisting of available funds, pending amounts in
458/// unconfirmed rounds, and pending exits.
459pub struct OffchainBalance {
460	/// Funds currently available for use. This reflects the spendable balance.
461	pub available: Amount,
462	/// Funds that are pending in unconfirmed operational rounds.
463	pub pending_in_round: Amount,
464	/// Funds being unilaterally exited. These may require more onchain confirmations to become
465	/// available onchain.
466	pub pending_exit: Amount,
467}
468
469/// Read-only properties of the Bark wallet.
470#[derive(Debug, Clone)]
471pub struct WalletProperties {
472	/// The Bitcoin network to run Bark on.
473	///
474	/// Default value: signet.
475	pub network: Network,
476
477	/// The wallet fingerpint
478	///
479	/// Used on wallet loading to check mnemonic correctness
480	pub fingerprint: Fingerprint,
481}
482
483/// Struct representing an extended private key derived from a
484/// wallet's seed, used to derive child VTXO keypairs
485///
486/// The VTXO seed is derived by applying a hardened derivation
487/// step at index 350 from the wallet's seed.
488pub struct VtxoSeed(bip32::Xpriv);
489
490impl VtxoSeed {
491	fn new(network: Network, seed: &[u8; 64]) -> Self {
492		let master = bip32::Xpriv::new_master(network, seed).unwrap();
493
494		Self(master.derive_priv(&SECP, &[ARK_PURPOSE_INDEX.into()]).unwrap())
495	}
496
497	fn fingerprint(&self) -> Fingerprint {
498		self.0.fingerprint(&SECP)
499	}
500
501	fn derive_keypair(&self, idx: u32) -> Keypair {
502		self.0.derive_priv(&SECP, &[idx.into()]).unwrap().to_keypair(&SECP)
503	}
504}
505
506/// The central entry point for using this library as an Ark wallet.
507///
508/// Overview
509/// - Wallet encapsulates the complete Ark client implementation:
510///   - address generation (Ark addresses/keys)
511///     - [Wallet::new_address],
512///     - [Wallet::new_address_with_index],
513///     - [Wallet::peak_address],
514///     - [Wallet::validate_arkoor_address]
515///   - boarding onchain funds into Ark from an onchain wallet (see [onchain::OnchainWallet])
516///     - [Wallet::board_amount],
517///     - [Wallet::board_all]
518///   - offboarding Ark funds to move them back onchain
519///     - [Wallet::offboard_vtxos],
520///     - [Wallet::offboard_all]
521///   - sending and receiving Ark payments (including to BOLT11/BOLT12 invoices)
522///     - [Wallet::send_arkoor_payment],
523///     - [Wallet::send_round_onchain_payment],
524///     - [Wallet::pay_lightning_invoice],
525///     - [Wallet::pay_lightning_address],
526///     - [Wallet::pay_lightning_offer]
527///   - tracking, selecting, and refreshing VTXOs
528///     - [Wallet::vtxos],
529///     - [Wallet::vtxos_with],
530///     - [Wallet::refresh_vtxos]
531///   - syncing with the Ark server, unilateral exits and performing general maintenance
532///     - [Wallet::maintenance]: Syncs everything offchain-related and refreshes VTXOs where
533///       necessary,
534///     - [Wallet::maintenance_with_onchain]: The same as [Wallet::maintenance] but also syncs the
535///       onchain wallet and unilateral exits,
536///     - [Wallet::maintenance_refresh]: Refreshes VTXOs where necessary without syncing anything,
537///     - [Wallet::sync]: Syncs network fee-rates, ark rounds and arkoor payments,
538///     - [Wallet::sync_exits]: Updates the status of unilateral exits,
539///     - [Wallet::sync_pending_lightning_send_vtxos]: Updates the status of pending lightning payments,
540///     - [Wallet::try_claim_all_lightning_receives]: Wait for payment receipt of all open invoices, then claim them,
541///     - [Wallet::sync_pending_boards]: Registers boards which are available for use
542///       in offchain payments
543///
544/// Key capabilities
545/// - Address management:
546///   - derive and peek deterministic Ark addresses and their indices
547/// - Funds lifecycle:
548///   - board funds from an external onchain wallet onto the Ark
549///   - send out-of-round Ark payments (arkoor)
550///   - offboard funds to onchain addresses
551///   - manage HTLCs and Lightning receives/sends
552/// - VTXO management:
553///   - query spendable and pending VTXOs
554///   - refresh expiring or risky VTXOs
555///   - compute balance broken down by spendable/pending states
556/// - Synchronization and maintenance:
557///   - sync against the Ark server and the onchain source
558///   - reconcile pending rounds, exits, and offchain state
559///   - periodic maintenance helpers (e.g., auto-register boards, refresh policies)
560///
561/// Construction and persistence
562/// - A [Wallet] is opened or created using a mnemonic and a backend implementing [BarkPersister].
563///   - [Wallet::create],
564///   - [Wallet::open]
565/// - Creation allows the use of an optional onchain wallet for boarding and [Exit] functionality.
566///   It also initializes any internal state and connects to the [onchain::ChainSource]. See
567///   [onchain::OnchainWallet] for an implementation of an onchain wallet using BDK.
568///   - [Wallet::create_with_onchain],
569///   - [Wallet::open_with_onchain]
570///
571/// Example
572/// ```
573/// # #[cfg(any(test, doc))]
574/// # async fn demo() -> anyhow::Result<()> {
575/// # use std::sync::Arc;
576/// # use bark::{Config, SqliteClient, Wallet};
577/// # use bark::onchain::OnchainWallet;
578/// # use bark::persist::BarkPersister;
579/// # use bark::persist::sqlite::helpers::in_memory_db;
580/// # use bip39::Mnemonic;
581/// # use bitcoin::Network;
582/// # let (db_path, _) = in_memory_db();
583/// let network = Network::Signet;
584/// let mnemonic = Mnemonic::generate(12)?;
585/// let cfg = Config {
586///   server_address: String::from("https://ark.signet.2nd.dev"),
587///   esplora_address: Some(String::from("https://esplora.signet.2nd.dev")),
588///   ..Default::default()
589/// };
590///
591/// // You can either use the included SQLite implementation or create your own.
592/// let persister = SqliteClient::open(db_path).await?;
593/// let db: Arc<dyn BarkPersister> = Arc::new(persister);
594///
595/// // Load or create an onchain wallet if needed
596/// let onchain_wallet = OnchainWallet::load_or_create(network, mnemonic.to_seed(""), db.clone())?;
597///
598/// // Create or open the Ark wallet
599/// let mut wallet = Wallet::create_with_onchain(
600/// 	&mnemonic,
601/// 	network,
602/// 	cfg.clone(),
603/// 	db,
604/// 	&onchain_wallet,
605/// 	false,
606/// ).await?;
607/// // let mut wallet = Wallet::create(&mnemonic, network, cfg.clone(), db.clone(), false).await?;
608/// // let mut wallet = Wallet::open(&mnemonic, db.clone(), cfg.clone()).await?;
609/// // let mut wallet = Wallet::open_with_onchain(
610/// //    &mnemonic, network, cfg.clone(), db.clone(), &onchain_wallet
611/// // ).await?;
612///
613/// // There are two main ways to update the wallet, the primary is to use one of the maintenance
614/// // commands which will sync everything, refresh VTXOs and reconcile pending lightning payments.
615/// wallet.maintenance().await?;
616/// wallet.maintenance_with_onchain(&mut onchain_wallet).await?;
617///
618/// // Alternatively, you can use the fine-grained sync commands to sync individual parts of the
619/// // wallet state and use `maintenance_refresh` where necessary to refresh VTXOs.
620/// wallet.sync().await?;
621/// wallet.sync_pending_lightning_send_vtxos().await?;
622/// wallet.register_all_confirmed_boards(&mut onchain_wallet).await?;
623/// wallet.sync_exits(&mut onchain_wallet).await?;
624/// wallet.maintenance_refresh().await?;
625///
626/// // Generate a new Ark address to receive funds via arkoor
627/// let addr = wallet.new_address()?;
628///
629/// // Query balance and VTXOs
630/// let balance = wallet.balance()?;
631/// let vtxos = wallet.vtxos()?;
632///
633/// // Progress any unilateral exits, make sure to sync first
634/// wallet.exit.progress_exit(&mut onchain_wallet, None).await?;
635///
636/// # Ok(())
637/// # }
638/// ```
639pub struct Wallet {
640	/// The chain source the wallet is connected to
641	pub chain: Arc<ChainSource>,
642
643	/// Exit subsystem handling unilateral exits and on-chain reconciliation outside Ark rounds.
644	pub exit: RwLock<Exit>,
645
646	/// Allows easy creation of and management of wallet fund movements.
647	pub movements: Arc<MovementManager>,
648
649	/// Active runtime configuration for networking, fees, policies and thresholds.
650	config: Config,
651
652	/// Persistence backend for wallet state (keys metadata, VTXOs, movements, round state, etc.).
653	db: Arc<dyn BarkPersister>,
654
655	/// Deterministic seed material used to derive VTXO ownership keypairs and addresses.
656	vtxo_seed: VtxoSeed,
657
658	/// Optional live connection to an Ark server for round participation and synchronization.
659	server: Option<ServerConnection>,
660
661	/// TODO: Replace this when we move to a modular subsystem architecture
662	subsystem_ids: HashMap<BarkSubsystem, SubsystemId>,
663}
664
665impl Wallet {
666	/// Creates a [onchain::ChainSource] instance to communicate with an onchain backend from the
667	/// given [Config].
668	pub fn chain_source(
669		config: &Config,
670	) -> anyhow::Result<onchain::ChainSourceSpec> {
671		if let Some(ref url) = config.esplora_address {
672			Ok(onchain::ChainSourceSpec::Esplora {
673				url: url.clone(),
674			})
675		} else if let Some(ref url) = config.bitcoind_address {
676			let auth = if let Some(ref c) = config.bitcoind_cookiefile {
677				bitcoin_ext::rpc::Auth::CookieFile(c.clone())
678			} else {
679				bitcoin_ext::rpc::Auth::UserPass(
680					config.bitcoind_user.clone().context("need bitcoind auth config")?,
681					config.bitcoind_pass.clone().context("need bitcoind auth config")?,
682				)
683			};
684			Ok(onchain::ChainSourceSpec::Bitcoind {
685				url: url.clone(),
686				auth,
687			})
688		} else {
689			bail!("Need to either provide esplora or bitcoind info");
690		}
691	}
692
693	/// Verifies that the bark [Wallet] can be used with the configured [onchain::ChainSource].
694	/// More specifically, if the [onchain::ChainSource] connects to Bitcoin Core it must be
695	/// a high enough version to support ephemeral anchors.
696	pub fn require_chainsource_version(&self) -> anyhow::Result<()> {
697		self.chain.require_version()
698	}
699
700	/// Derive and store the keypair directly after currently last revealed one,
701	/// together with its index.
702	pub fn derive_store_next_keypair(&self) -> anyhow::Result<(Keypair, u32)> {
703		let last_revealed = self.db.get_last_vtxo_key_index()?;
704
705		let index = last_revealed.map(|i| i + 1).unwrap_or(u32::MIN);
706		let keypair = self.vtxo_seed.derive_keypair(index);
707
708		self.db.store_vtxo_key(index, keypair.public_key())?;
709		Ok((keypair, index))
710	}
711
712	/// Retrieves a keypair based on the provided index and checks if the corresponding public key
713	/// exists in the [Vtxo] database.
714	///
715	/// # Arguments
716	///
717	/// * `index` - The index used to derive a keypair.
718	///
719	/// # Returns
720	///
721	/// * `Ok(Keypair)` - If the keypair is successfully derived and its public key exists in the
722	///   database.
723	/// * `Err(anyhow::Error)` - If the public key does not exist in the database or if an error
724	///   occurs during the database query.
725	pub fn peak_keypair(&self, index: u32) -> anyhow::Result<Keypair> {
726		let keypair = self.vtxo_seed.derive_keypair(index);
727		if self.db.get_public_key_idx(&keypair.public_key())?.is_some() {
728			Ok(keypair)
729		} else {
730			bail!("VTXO key {} does not exist, please derive it first", index)
731		}
732	}
733
734
735	/// Retrieves the [Keypair] for a provided [PublicKey]
736	///
737	/// # Arguments
738	///
739	/// * `public_key` - The public key for which the keypair must be found
740	///
741	/// # Returns
742	/// * `Ok(Some(u32, Keypair))` - If the pubkey is found, the derivation-index and keypair are
743	///                              returned
744	/// * `Ok(None)` - If the pubkey cannot be found in the database
745	/// * `Err(anyhow::Error)` - If an error occurred related to the database query
746	pub fn pubkey_keypair(&self, public_key: &PublicKey) -> anyhow::Result<Option<(u32, Keypair)>> {
747		if let Some(index) = self.db.get_public_key_idx(&public_key)? {
748			Ok(Some((index, self.vtxo_seed.derive_keypair(index))))
749		} else {
750			Ok(None)
751		}
752	}
753
754	/// Retrieves the [Keypair] for a provided [Vtxo]
755	///
756	/// # Arguments
757	///
758	/// * `vtxo` - The vtxo for which the key must be found
759	///
760	/// # Returns
761	/// * `Ok(Some(Keypair))` - If the pubkey is found, the keypair is returned
762	/// * `Err(anyhow::Error)` - If the corresponding public key doesn't exist
763	///   in the database or a database error occurred.
764	pub fn get_vtxo_key(&self, vtxo: &Vtxo) -> anyhow::Result<Keypair> {
765		let idx = self.db.get_public_key_idx(&vtxo.user_pubkey())?
766			.context("VTXO key not found")?;
767		Ok(self.vtxo_seed.derive_keypair(idx))
768	}
769
770	/// Generate a new [ark::Address].
771	pub fn new_address(&self) -> anyhow::Result<ark::Address> {
772		let ark = &self.require_server()?;
773		let network = self.properties()?.network;
774		let pubkey = self.derive_store_next_keypair()?.0.public_key();
775
776		Ok(ark::Address::builder()
777			.testnet(network != bitcoin::Network::Bitcoin)
778			.server_pubkey(ark.info.server_pubkey)
779			.pubkey_policy(pubkey)
780			.into_address().unwrap())
781	}
782
783	/// Peak for an [ark::Address] at the given key index.
784	///
785	/// May return an error if the address at the given index has not been derived yet.
786	pub fn peak_address(&self, index: u32) -> anyhow::Result<ark::Address> {
787		let ark = &self.require_server()?;
788		let network = self.properties()?.network;
789		let pubkey = self.peak_keypair(index)?.public_key();
790
791		Ok(ark::Address::builder()
792			.testnet(network != Network::Bitcoin)
793			.server_pubkey(ark.info.server_pubkey)
794			.pubkey_policy(pubkey)
795			.into_address().unwrap())
796	}
797
798	/// Generate a new [ark::Address] and returns the index of the key used to create it.
799	///
800	/// This derives and stores the keypair directly after currently last revealed one.
801	pub fn new_address_with_index(&self) -> anyhow::Result<(ark::Address, u32)> {
802		let ark = &self.require_server()?;
803		let network = self.properties()?.network;
804		let (keypair, index) = self.derive_store_next_keypair()?;
805		let pubkey = keypair.public_key();
806		let addr = ark::Address::builder()
807			.testnet(network != bitcoin::Network::Bitcoin)
808			.server_pubkey(ark.info.server_pubkey)
809			.pubkey_policy(pubkey)
810			.into_address()?;
811		Ok((addr, index))
812	}
813
814	/// Create a new wallet without an optional onchain backend. This will restrict features such as
815	/// boarding and unilateral exit.
816	///
817	/// The `force` flag will allow you to create the wallet even if a connection to the Ark server
818	/// cannot be established, it will not overwrite a wallet which has already been created.
819	pub async fn create(
820		mnemonic: &Mnemonic,
821		network: Network,
822		config: Config,
823		db: Arc<dyn BarkPersister>,
824		force: bool,
825	) -> anyhow::Result<Wallet> {
826		trace!("Config: {:?}", config);
827		if let Some(existing) = db.read_properties()? {
828			trace!("Existing config: {:?}", existing);
829			bail!("cannot overwrite already existing config")
830		}
831
832		if !force {
833			if let Err(err) = ServerConnection::connect(&config.server_address, network).await {
834				bail!("Failed to connect to provided server (if you are sure use the --force flag): {}", err);
835			}
836		}
837
838		let wallet_fingerprint = VtxoSeed::new(network, &mnemonic.to_seed("")).fingerprint();
839		let properties = WalletProperties {
840			network: network,
841			fingerprint: wallet_fingerprint,
842		};
843
844		// write the config to db
845		db.init_wallet(&properties).context("cannot init wallet in the database")?;
846		info!("Created wallet with fingerprint: {}", wallet_fingerprint);
847
848		// from then on we can open the wallet
849		let wallet = Wallet::open(&mnemonic, db, config).await.context("failed to open wallet")?;
850		wallet.require_chainsource_version()?;
851
852		Ok(wallet)
853	}
854
855	/// Create a new wallet with an onchain backend. This enables full Ark functionality. A default
856	/// implementation of an onchain wallet when the `onchain_bdk` feature is enabled. See
857	/// [onchain::OnchainWallet] for more details. Alternatively, implement [ExitUnilaterally] if
858	/// you have your own onchain wallet implementation.
859	///
860	/// The `force` flag will allow you to create the wallet even if a connection to the Ark server
861	/// cannot be established, it will not overwrite a wallet which has already been created.
862	pub async fn create_with_onchain(
863		mnemonic: &Mnemonic,
864		network: Network,
865		config: Config,
866		db: Arc<dyn BarkPersister>,
867		onchain: &dyn ExitUnilaterally,
868		force: bool,
869	) -> anyhow::Result<Wallet> {
870		let mut wallet = Wallet::create(mnemonic, network, config, db, force).await?;
871		wallet.exit.get_mut().load(onchain).await?;
872		Ok(wallet)
873	}
874
875	/// Loads the bark wallet from the given database ensuring the fingerprint remains consistent.
876	pub async fn open(
877		mnemonic: &Mnemonic,
878		db: Arc<dyn BarkPersister>,
879		config: Config,
880	) -> anyhow::Result<Wallet> {
881		let properties = db.read_properties()?.context("Wallet is not initialised")?;
882
883		let seed = mnemonic.to_seed("");
884		let vtxo_seed = VtxoSeed::new(properties.network, &seed);
885
886		if properties.fingerprint != vtxo_seed.fingerprint() {
887			bail!("incorrect mnemonic")
888		}
889
890		let chain_source = if let Some(ref url) = config.esplora_address {
891			onchain::ChainSourceSpec::Esplora {
892				url: url.clone(),
893			}
894		} else if let Some(ref url) = config.bitcoind_address {
895			let auth = if let Some(ref c) = config.bitcoind_cookiefile {
896				bitcoin_ext::rpc::Auth::CookieFile(c.clone())
897			} else {
898				bitcoin_ext::rpc::Auth::UserPass(
899					config.bitcoind_user.clone().context("need bitcoind auth config")?,
900					config.bitcoind_pass.clone().context("need bitcoind auth config")?,
901				)
902			};
903			onchain::ChainSourceSpec::Bitcoind { url: url.clone(), auth }
904		} else {
905			bail!("Need to either provide esplora or bitcoind info");
906		};
907
908		let chain_source_client = ChainSource::new(
909			chain_source, properties.network, config.fallback_fee_rate,
910		).await?;
911		let chain = Arc::new(chain_source_client);
912
913		let server = match ServerConnection::connect(
914			&config.server_address, properties.network,
915		).await {
916			Ok(s) => Some(s),
917			Err(e) => {
918				warn!("Ark server handshake failed: {}", e);
919				None
920			}
921		};
922
923		let movements = Arc::new(MovementManager::new(db.clone()));
924		let exit = RwLock::new(Exit::new(db.clone(), chain.clone(), movements.clone()).await?);
925		let mut subsystem_ids = HashMap::new();
926		{
927			let subsystems = [
928				BarkSubsystem::Arkoor,
929				BarkSubsystem::Board,
930				BarkSubsystem::LightningReceive,
931				BarkSubsystem::LightningSend,
932				BarkSubsystem::Round,
933			];
934			for subsystem in subsystems.into_iter() {
935				let id = movements.register_subsystem(subsystem.as_str().into()).await?;
936				subsystem_ids.insert(subsystem, id);
937			}
938		};
939
940		Ok(Wallet { config, db, vtxo_seed, exit, movements, server, chain, subsystem_ids })
941	}
942
943	/// Similar to [Wallet::open] however this also unilateral exits using the provided onchain
944	/// wallet.
945	pub async fn open_with_onchain(
946		mnemonic: &Mnemonic,
947		db: Arc<dyn BarkPersister>,
948		onchain: &dyn ExitUnilaterally,
949		cfg: Config,
950	) -> anyhow::Result<Wallet> {
951		let mut wallet = Wallet::open(mnemonic, db, cfg).await?;
952		wallet.exit.get_mut().load(onchain).await?;
953		Ok(wallet)
954	}
955
956	/// Returns the config used to create/load the bark [Wallet].
957	pub fn config(&self) -> &Config {
958		&self.config
959	}
960
961	/// Retrieves the [WalletProperties] of the current bark [Wallet].
962	pub fn properties(&self) -> anyhow::Result<WalletProperties> {
963		let properties = self.db.read_properties()?.context("Wallet is not initialised")?;
964		Ok(properties)
965	}
966
967	fn require_server(&self) -> anyhow::Result<ServerConnection> {
968		self.server.clone().context("You should be connected to Ark server to perform this action")
969	}
970
971	pub async fn check_connection(&self) -> anyhow::Result<()> {
972		let mut server = self.require_server()?;
973		server.client.handshake(protos::HandshakeRequest {
974			bark_version: Some(env!("CARGO_PKG_VERSION").into()),
975		}).await?;
976
977		Ok(())
978	}
979
980	/// Return [ArkInfo] fetched on last handshake with the Ark server
981	pub fn ark_info(&self) -> Option<&ArkInfo> {
982		self.server.as_ref().map(|a| &a.info)
983	}
984
985	/// Return the [Balance] of the wallet.
986	///
987	/// Make sure you sync before calling this method.
988	pub fn balance(&self) -> anyhow::Result<Balance> {
989		let vtxos = self.vtxos()?;
990
991		let spendable = {
992			let mut v = vtxos.iter().collect();
993			VtxoStateKind::Spendable.filter_vtxos(&mut v)?;
994			v.into_iter().map(|v| v.amount()).sum::<Amount>()
995		};
996
997		let pending_lightning_send = self.pending_lightning_send_vtxos()?.iter().map(|v| v.amount())
998			.sum::<Amount>();
999
1000		let pending_lightning_receive = self.pending_lightning_receive_balance()?;
1001
1002		let pending_board = self.pending_board_vtxos()?.iter().map(|v| v.amount()).sum::<Amount>();
1003
1004		let pending_in_round = self.pending_round_input_vtxos()?.iter().map(|v| v.amount()).sum();
1005
1006		let pending_exit = self.exit.try_read().ok().map(|e| e.pending_total());
1007
1008		Ok(Balance {
1009			spendable,
1010			pending_in_round,
1011			pending_lightning_send,
1012			pending_lightning_receive,
1013			pending_exit,
1014			pending_board,
1015		})
1016	}
1017
1018	/// Fetches [Vtxo]'s funding transaction and validates the VTXO against it.
1019	pub async fn validate_vtxo(&self, vtxo: &Vtxo) -> anyhow::Result<()> {
1020		let tx = self.chain.get_tx(&vtxo.chain_anchor().txid).await
1021			.context("could not fetch chain tx")?;
1022
1023		let tx = tx.with_context(|| {
1024			format!("vtxo chain anchor not found for vtxo: {}", vtxo.chain_anchor().txid)
1025		})?;
1026
1027		vtxo.validate(&tx)?;
1028
1029		Ok(())
1030	}
1031
1032	/// Retrieves the full state of a [Vtxo] for a given [VtxoId] if it exists in the database.
1033	pub fn get_vtxo_by_id(&self, vtxo_id: VtxoId) -> anyhow::Result<WalletVtxo> {
1034		let vtxo = self.db.get_wallet_vtxo(vtxo_id)
1035			.with_context(|| format!("Error when querying vtxo {} in database", vtxo_id))?
1036			.with_context(|| format!("The VTXO with id {} cannot be found", vtxo_id))?;
1037		Ok(vtxo)
1038	}
1039
1040	/// Fetches all wallet fund movements ordered from newest to oldest.
1041	pub fn movements(&self) -> anyhow::Result<Vec<Movement>> {
1042		Ok(self.db.get_movements()?)
1043	}
1044
1045	/// Returns all VTXOs from the database.
1046	pub fn all_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1047		Ok(self.db.get_all_vtxos()?)
1048	}
1049
1050	/// Returns all not spent vtxos
1051	pub fn vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1052		Ok(self.db.get_vtxos_by_state(&UNSPENT_STATES)?)
1053	}
1054
1055	/// Returns all vtxos matching the provided predicate
1056	pub fn vtxos_with(&self, filter: &impl FilterVtxos) -> anyhow::Result<Vec<WalletVtxo>> {
1057		let mut vtxos = self.vtxos()?;
1058		filter.filter_vtxos(&mut vtxos).context("error filtering vtxos")?;
1059		Ok(vtxos)
1060	}
1061
1062	/// Returns all spendable vtxos
1063	pub fn spendable_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1064		Ok(self.vtxos_with(&VtxoStateKind::Spendable)?)
1065	}
1066
1067	/// Returns all spendable vtxos matching the provided predicate
1068	pub fn spendable_vtxos_with(
1069		&self,
1070		filter: &impl FilterVtxos,
1071	) -> anyhow::Result<Vec<WalletVtxo>> {
1072		let mut vtxos = self.spendable_vtxos()?;
1073		filter.filter_vtxos(&mut vtxos).context("error filtering vtxos")?;
1074		Ok(vtxos)
1075	}
1076
1077	/// Queries the database for any VTXO that is an unregistered board. There is a lag time between
1078	/// when a board is created and when it becomes spendable.
1079	///
1080	/// See [ArkInfo::required_board_confirmations] and [Wallet::sync_pending_boards].
1081	pub fn pending_board_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1082		let vtxos = self.db.get_all_pending_board_ids()?.iter()
1083			.map(|vtxo_id| self.get_vtxo_by_id(*vtxo_id))
1084			.collect::<anyhow::Result<Vec<_>>>()?;
1085
1086		debug_assert!(vtxos.iter().all(|v| matches!(v.state.kind(), VtxoStateKind::Locked)),
1087			"all pending board vtxos should be locked"
1088		);
1089
1090		Ok(vtxos)
1091	}
1092
1093	/// Returns all VTXOs that are locked in a pending round
1094	///
1095	/// This excludes all input VTXOs for which the output VTXOs have already
1096	/// been created.
1097	pub fn pending_round_input_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1098		let mut ret = Vec::new();
1099		for round in self.db.load_round_states()? {
1100			let inputs = round.state.locked_pending_inputs();
1101			ret.reserve(inputs.len());
1102			for input in inputs {
1103				ret.push(self.get_vtxo_by_id(input.id()).context("unknown round input VTXO")?);
1104			}
1105		}
1106		Ok(ret)
1107	}
1108
1109	/// Queries the database for any VTXO that is an pending lightning send.
1110	pub fn pending_lightning_send_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1111		let vtxos = self.db.get_all_pending_lightning_send()?.into_iter()
1112			.flat_map(|pending_lightning_send| pending_lightning_send.htlc_vtxos)
1113			.collect::<Vec<_>>();
1114
1115		Ok(vtxos)
1116	}
1117
1118	/// Returns all vtxos that will expire within `threshold` blocks
1119	pub async fn get_expiring_vtxos(
1120		&self,
1121		threshold: BlockHeight,
1122	) -> anyhow::Result<Vec<WalletVtxo>> {
1123		let expiry = self.chain.tip().await? + threshold;
1124		let filter = VtxoFilter::new(&self).expires_before(expiry);
1125		Ok(self.spendable_vtxos_with(&filter)?)
1126	}
1127
1128	/// Attempts to register all pendings boards with the Ark server. A board transaction must have
1129	/// sufficient confirmations before it will be registered. For more details see
1130	/// [ArkInfo::required_board_confirmations].
1131	pub async fn sync_pending_boards(&self) -> anyhow::Result<()> {
1132		let ark_info = self.require_server()?.info;
1133		let current_height = self.chain.tip().await?;
1134		let unregistered_boards = self.pending_board_vtxos()?;
1135		let mut registered_boards = 0;
1136
1137		if unregistered_boards.is_empty() {
1138			return Ok(());
1139		}
1140
1141		trace!("Attempting registration of sufficiently confirmed boards");
1142
1143		for board in unregistered_boards {
1144			let anchor = board.vtxo.chain_anchor();
1145			let confs = match self.chain.tx_status(anchor.txid).await {
1146				Ok(TxStatus::Confirmed(block_ref)) => Some(current_height - (block_ref.height - 1)),
1147				Ok(TxStatus::Mempool) => Some(0),
1148				Ok(TxStatus::NotFound) => None,
1149				Err(_) => None,
1150			};
1151
1152			if let Some(confs) = confs {
1153				if confs >= ark_info.required_board_confirmations as BlockHeight {
1154					if let Err(e) = self.register_board(board.vtxo.id()).await {
1155						warn!("Failed to register board {}: {}", board.vtxo.id(), e);
1156					} else {
1157						info!("Registered board {}", board.vtxo.id());
1158						registered_boards += 1;
1159					}
1160				}
1161			}
1162		};
1163
1164		if registered_boards > 0 {
1165			info!("Registered {registered_boards} sufficiently confirmed boards");
1166		}
1167		Ok(())
1168	}
1169
1170	/// Performs maintenance tasks on the offchain wallet.
1171	///
1172	/// This can take a long period of time due to syncing rounds, arkoors, checking pending
1173	/// payments and refreshing VTXOs if necessary.
1174	pub async fn maintenance(&self) -> anyhow::Result<()> {
1175		info!("Starting wallet maintenance");
1176		self.sync().await;
1177		self.maintenance_refresh().await?;
1178		Ok(())
1179	}
1180
1181	/// Performs maintenance tasks on the onchain and offchain wallet.
1182	///
1183	/// This can take a long period of time due to syncing the onchain wallet, registering boards,
1184	/// syncing rounds, arkoors, and the exit system, checking pending lightning payments and
1185	/// refreshing VTXOs if necessary.
1186	pub async fn maintenance_with_onchain<W: PreparePsbt + SignPsbt + ExitUnilaterally>(
1187		&self,
1188		onchain: &mut W,
1189	) -> anyhow::Result<()> {
1190		info!("Starting wallet maintenance with onchain wallet");
1191		self.sync().await;
1192		self.maintenance_refresh().await?;
1193
1194		// NB: order matters here, after syncing lightning, we might have new exits to start
1195		self.sync_exits(onchain).await?;
1196
1197		Ok(())
1198	}
1199
1200	/// Performs a refresh of all VTXOs that are due to be refreshed, if any. This will include any
1201	/// VTXOs within the expiry threshold ([Config::vtxo_refresh_expiry_threshold]) or those which
1202	/// are uneconomical to exit due to onchain network conditions.
1203	///
1204	/// Returns a [RoundId] if a refresh occurs.
1205	pub async fn maintenance_refresh(&self) -> anyhow::Result<Option<RoundStatus>> {
1206		let vtxos = self.get_vtxos_to_refresh().await?.into_iter()
1207			.map(|v| v.id())
1208			.collect::<Vec<_>>();
1209		if vtxos.len() == 0 {
1210			return Ok(None);
1211		}
1212
1213		info!("Performing maintenance refresh");
1214		self.refresh_vtxos(vtxos).await
1215	}
1216
1217	/// Sync offchain wallet and update onchain fees. This is a much more lightweight alternative
1218	/// to [Wallet::maintenance] as it will not refresh VTXOs or sync the onchain wallet.
1219	///
1220	/// Notes:
1221	/// - The exit system will not be synced as doing so requires the onchain wallet.
1222	pub async fn sync(&self) {
1223		tokio::join!(
1224			async {
1225				// NB: order matters here, if syncing call fails,
1226				// we still want to update the fee rates
1227				if let Err(e) = self.chain.update_fee_rates(self.config.fallback_fee_rate).await {
1228					warn!("Error updating fee rates: {:#}", e);
1229				}
1230			},
1231			async {
1232				if let Err(e) = self.sync_oors().await {
1233					warn!("Error in arkoor sync: {:#}", e);
1234				}
1235			},
1236			async {
1237				if let Err(e) = self.sync_pending_rounds().await {
1238					warn!("Error while trying to progress rounds awaiting confirmations: {:#}", e);
1239				}
1240			},
1241			async {
1242				if let Err(e) = self.sync_pending_lightning_send_vtxos().await {
1243					warn!("Error syncing pending lightning payments: {:#}", e);
1244				}
1245			},
1246			async {
1247				if let Err(e) = self.claim_all_pending_htlc_receives().await {
1248					warn!("Error claiming pending lightning receives: {:#}", e);
1249				}
1250			},
1251			async {
1252				if let Err(e) = self.sync_pending_boards().await {
1253					warn!("Error syncing pending boards: {:#}", e);
1254				}
1255			}
1256		);
1257	}
1258
1259	/// Sync the transaction status of unilateral exits
1260	///
1261	/// This will not progress the unilateral exits in any way, it will merely check the
1262	/// transaction status of each transaction as well as check whether any exits have become
1263	/// claimable or have been claimed.
1264	pub async fn sync_exits(
1265		&self,
1266		onchain: &mut dyn ExitUnilaterally,
1267	) -> anyhow::Result<()> {
1268		self.exit.write().await.sync_exit(onchain).await?;
1269		Ok(())
1270	}
1271
1272	pub fn pending_lightning_sends(&self) -> anyhow::Result<Vec<PendingLightningSend>> {
1273		Ok(self.db.get_all_pending_lightning_send()?)
1274	}
1275
1276	/// Syncs pending lightning payments, verifying whether the payment status has changed and
1277	/// creating a revocation VTXO if necessary.
1278	pub async fn sync_pending_lightning_send_vtxos(&self) -> anyhow::Result<()> {
1279		let pending_payments = self.pending_lightning_sends()?;
1280
1281		if pending_payments.is_empty() {
1282			return Ok(());
1283		}
1284
1285		info!("Syncing {} pending lightning sends", pending_payments.len());
1286
1287		for payment in pending_payments {
1288			self.check_lightning_payment(&payment).await?;
1289		}
1290
1291		Ok(())
1292	}
1293
1294	/// Drop a specific [Vtxo] from the database. This is destructive and will result in a loss of
1295	/// funds.
1296	pub async fn dangerous_drop_vtxo(&self, vtxo_id: VtxoId) -> anyhow::Result<()> {
1297		warn!("Drop vtxo {} from the database", vtxo_id);
1298		self.db.remove_vtxo(vtxo_id)?;
1299		Ok(())
1300	}
1301
1302	/// Drop all VTXOs from the database. This is destructive and will result in a loss of funds.
1303	//TODO(stevenroose) improve the way we expose dangerous methods
1304	pub async fn dangerous_drop_all_vtxos(&self) -> anyhow::Result<()> {
1305		warn!("Dropping all vtxos from the db...");
1306		for vtxo in self.vtxos()? {
1307			self.db.remove_vtxo(vtxo.id())?;
1308		}
1309
1310		self.exit.write().await.clear_exit()?;
1311		Ok(())
1312	}
1313
1314	/// Board a [Vtxo] with the given amount.
1315	///
1316	/// NB we will spend a little more onchain to cover fees.
1317	pub async fn board_amount(
1318		&self,
1319		onchain: &mut dyn onchain::Board,
1320		amount: Amount,
1321	) -> anyhow::Result<Board> {
1322		let (user_keypair, _) = self.derive_store_next_keypair()?;
1323		self.board(onchain, Some(amount), user_keypair).await
1324	}
1325
1326	/// Board a [Vtxo] with all the funds in your onchain wallet.
1327	pub async fn board_all(
1328		&self,
1329		onchain: &mut dyn onchain::Board,
1330	) -> anyhow::Result<Board> {
1331		let (user_keypair, _) = self.derive_store_next_keypair()?;
1332		self.board(onchain, None, user_keypair).await
1333	}
1334
1335	async fn board(
1336		&self,
1337		wallet: &mut dyn onchain::Board,
1338		amount: Option<Amount>,
1339		user_keypair: Keypair,
1340	) -> anyhow::Result<Board> {
1341		let mut srv = self.require_server()?;
1342		let properties = self.db.read_properties()?.context("Missing config")?;
1343		let current_height = self.chain.tip().await?;
1344
1345		let expiry_height = current_height + srv.info.vtxo_expiry_delta as BlockHeight;
1346		let builder = BoardBuilder::new(
1347			user_keypair.public_key(),
1348			expiry_height,
1349			srv.info.server_pubkey,
1350			srv.info.vtxo_exit_delta,
1351		);
1352
1353		let addr = bitcoin::Address::from_script(
1354			&builder.funding_script_pubkey(),
1355			properties.network,
1356		)?;
1357
1358		// We create the board tx template, but don't sign it yet.
1359		let fee_rate = self.chain.fee_rates().await.regular;
1360		let (board_psbt, amount) = if let Some(amount) = amount {
1361			let psbt = wallet.prepare_tx(&[(addr, amount)], fee_rate)?;
1362			(psbt, amount)
1363		} else {
1364			let psbt = wallet.prepare_drain_tx(addr, fee_rate)?;
1365			assert_eq!(psbt.unsigned_tx.output.len(), 1);
1366			let amount = psbt.unsigned_tx.output[0].value;
1367			(psbt, amount)
1368		};
1369
1370		ensure!(amount >= srv.info.min_board_amount,
1371			"board amount of {amount} is less than minimum board amount required by server ({})",
1372			srv.info.min_board_amount,
1373		);
1374
1375		let utxo = OutPoint::new(board_psbt.unsigned_tx.compute_txid(), BOARD_FUNDING_TX_VTXO_VOUT);
1376		let builder = builder
1377			.set_funding_details(amount, utxo)
1378			.generate_user_nonces();
1379
1380		let cosign_resp = srv.client.request_board_cosign(protos::BoardCosignRequest {
1381			amount: amount.to_sat(),
1382			utxo: bitcoin::consensus::serialize(&utxo), //TODO(stevenroose) change to own
1383			expiry_height,
1384			user_pubkey: user_keypair.public_key().serialize().to_vec(),
1385			pub_nonce: builder.user_pub_nonce().serialize().to_vec(),
1386		}).await.context("error requesting board cosign")?
1387			.into_inner().try_into().context("invalid cosign response from server")?;
1388
1389		ensure!(builder.verify_cosign_response(&cosign_resp),
1390			"invalid board cosignature received from server",
1391		);
1392
1393		// Store vtxo first before we actually make the on-chain tx.
1394		let vtxo = builder.build_vtxo(&cosign_resp, &user_keypair)?;
1395
1396		let onchain_fee = board_psbt.fee()?;
1397		let movement_id = self.movements.new_movement(
1398			self.subsystem_ids[&BarkSubsystem::Board],
1399			BoardMovement::Board.to_string(),
1400		).await?;
1401		self.movements.update_movement(
1402			movement_id,
1403			MovementUpdate::new()
1404				.produced_vtxo(&vtxo)
1405				.intended_and_effective_balance(vtxo.amount().to_signed()?)
1406				.metadata(BoardMovement::metadata(utxo, onchain_fee)?),
1407		).await?;
1408		self.store_locked_vtxos([&vtxo], Some(movement_id))?;
1409
1410		let tx = wallet.finish_tx(board_psbt)?;
1411		self.db.store_pending_board(vtxo.id(), &tx, movement_id)?;
1412
1413		trace!("Broadcasting board tx: {}", bitcoin::consensus::encode::serialize_hex(&tx));
1414		self.chain.broadcast_tx(&tx).await?;
1415
1416		info!("Board broadcasted");
1417		Ok(Board {
1418			funding_txid: tx.compute_txid(),
1419			vtxos: vec![vtxo.into()],
1420		})
1421	}
1422
1423	/// Registers a board to the Ark server
1424	async fn register_board(&self, vtxo: impl VtxoRef) -> anyhow::Result<Board> {
1425		trace!("Attempting to register board {} to server", vtxo.vtxo_id());
1426		let mut srv = self.require_server()?;
1427
1428		// Get the vtxo and funding transaction from the database
1429		let vtxo = match vtxo.vtxo() {
1430			Some(v) => v,
1431			None => {
1432				&self.db.get_wallet_vtxo(vtxo.vtxo_id())?
1433					.with_context(|| format!("VTXO doesn't exist: {}", vtxo.vtxo_id()))?
1434			},
1435		};
1436
1437		// Register the vtxo with the server
1438		srv.client.register_board_vtxo(protos::BoardVtxoRequest {
1439			board_vtxo: vtxo.serialize(),
1440		}).await.context("error registering board with the Ark server")?;
1441
1442		// Remember that we have stored the vtxo
1443		// No need to complain if the vtxo is already registered
1444		self.db.update_vtxo_state_checked(vtxo.id(), VtxoState::Spendable, &UNSPENT_STATES)?;
1445
1446		let movement_id = self.db.get_pending_board_movement_id(vtxo.id())?;
1447		self.db.remove_pending_board(&vtxo.id())?;
1448
1449		self.movements.finish_movement(movement_id, MovementStatus::Finished).await?;
1450
1451		let funding_txid = vtxo.chain_anchor().txid;
1452
1453		Ok(Board {
1454			funding_txid,
1455			vtxos: vec![vtxo.clone()],
1456		})
1457	}
1458
1459	/// Checks if the provided VTXO has some counterparty risk in the current wallet
1460	///
1461	/// An arkoor vtxo is considered to have some counterparty risk
1462	/// if it is (directly or not) based on round VTXOs that aren't owned by the wallet
1463	fn has_counterparty_risk(&self, vtxo: &Vtxo) -> anyhow::Result<bool> {
1464		for past_pk in vtxo.past_arkoor_pubkeys() {
1465			if !self.db.get_public_key_idx(&past_pk)?.is_some() {
1466				return Ok(true);
1467			}
1468		}
1469		Ok(!self.db.get_public_key_idx(&vtxo.user_pubkey())?.is_some())
1470	}
1471
1472	pub async fn sync_oors(&self) -> anyhow::Result<()> {
1473		let last_pk_index = self.db.get_last_vtxo_key_index()?.unwrap_or_default();
1474		let pubkeys = (0..=last_pk_index).map(|idx| {
1475			self.vtxo_seed.derive_keypair(idx).public_key()
1476		}).collect::<Vec<_>>();
1477
1478		self.sync_arkoor_for_pubkeys(&pubkeys).await?;
1479
1480		Ok(())
1481	}
1482
1483	/// Sync with the Ark server and look for out-of-round received VTXOs by public key.
1484	async fn sync_arkoor_for_pubkeys(
1485		&self,
1486		public_keys: &[PublicKey],
1487	) -> anyhow::Result<()> {
1488		let mut srv = self.require_server()?;
1489
1490		for pubkeys in public_keys.chunks(rpc::MAX_NB_MAILBOX_PUBKEYS) {
1491			// Then sync OOR vtxos.
1492			debug!("Emptying OOR mailbox at Ark server...");
1493			let req = protos::ArkoorVtxosRequest {
1494				pubkeys: pubkeys.iter().map(|pk| pk.serialize().to_vec()).collect(),
1495			};
1496			let packages = srv.client.empty_arkoor_mailbox(req).await
1497				.context("error fetching oors")?.into_inner().packages;
1498			debug!("Ark server has {} arkoor packages for us", packages.len());
1499
1500			for package in packages {
1501				let mut vtxos = Vec::with_capacity(package.vtxos.len());
1502				for vtxo in package.vtxos {
1503					let vtxo = match Vtxo::deserialize(&vtxo) {
1504						Ok(vtxo) => vtxo,
1505						Err(e) => {
1506							warn!("Invalid vtxo from Ark server: {}", e);
1507							continue;
1508						}
1509					};
1510
1511					if let Err(e) = self.validate_vtxo(&vtxo).await {
1512						error!("Received invalid arkoor VTXO from server: {}", e);
1513						continue;
1514					}
1515
1516					match self.db.has_spent_vtxo(vtxo.id()) {
1517						Ok(spent) if spent => {
1518							debug!("Not adding OOR vtxo {} because it is considered spent", vtxo.id());
1519							continue;
1520						},
1521						_ => {}
1522					}
1523
1524					if let Ok(Some(_)) = self.db.get_wallet_vtxo(vtxo.id()) {
1525						debug!("Not adding OOR vtxo {} because it already exists", vtxo.id());
1526						continue;
1527					}
1528
1529					vtxos.push(vtxo);
1530				}
1531
1532				self.store_spendable_vtxos(&vtxos)?;
1533				self.movements.new_finished_movement(
1534					self.subsystem_ids[&BarkSubsystem::Arkoor],
1535					ArkoorMovement::Receive.to_string(),
1536					MovementStatus::Finished,
1537					MovementUpdate::new()
1538						.produced_vtxos(&vtxos)
1539						.intended_and_effective_balance(
1540							vtxos
1541							.iter()
1542							.map(|vtxo| vtxo.amount()).sum::<Amount>()
1543							.to_signed()?,
1544						),
1545				).await?;
1546			}
1547		}
1548
1549		Ok(())
1550	}
1551
1552	/// If there are any VTXOs that match the "should-refresh" condition with
1553	/// a total value over the  p2tr dust limit, they are added to the round
1554	/// participation and an additional output is also created.
1555	async fn add_should_refresh_vtxos(
1556		&self,
1557		participation: &mut RoundParticipation,
1558	) -> anyhow::Result<()> {
1559		let excluded_ids = participation.inputs.iter().map(|v| v.vtxo_id())
1560			.collect::<HashSet<_>>();
1561
1562		let should_refresh_vtxos = self.get_vtxos_to_refresh().await?.into_iter()
1563			.filter(|v| !excluded_ids.contains(&v.id()))
1564			.map(|v| v.vtxo).collect::<Vec<_>>();
1565
1566
1567		let total_amount = should_refresh_vtxos.iter().map(|v| v.amount()).sum::<Amount>();
1568
1569		if total_amount > P2TR_DUST {
1570			let (user_keypair, _) = self.derive_store_next_keypair()?;
1571			let req = VtxoRequest {
1572				policy: VtxoPolicy::new_pubkey(user_keypair.public_key()),
1573				amount: total_amount,
1574			};
1575
1576			participation.inputs.extend(should_refresh_vtxos);
1577			participation.outputs.push(req);
1578		}
1579
1580		Ok(())
1581	}
1582
1583	pub fn build_offboard_participation<V: VtxoRef>(
1584		&self,
1585		vtxos: impl IntoIterator<Item = V>,
1586		destination: ScriptBuf,
1587	) -> anyhow::Result<RoundParticipation> {
1588		let srv = self.require_server()?;
1589
1590		let vtxos = {
1591			let vtxos = vtxos.into_iter();
1592			let mut ret = Vec::with_capacity(vtxos.size_hint().0);
1593			for v in vtxos {
1594				let vtxo = match v.vtxo() {
1595					Some(v) => v.clone(),
1596					None => self.get_vtxo_by_id(v.vtxo_id()).context("vtxo not found")?.vtxo,
1597				};
1598				ret.push(vtxo);
1599			}
1600			ret
1601		};
1602
1603		if vtxos.is_empty() {
1604			bail!("no VTXO to offboard");
1605		}
1606
1607		let fee = OffboardRequest::calculate_fee(&destination, srv.info.offboard_feerate)
1608			.expect("bdk created invalid scriptPubkey");
1609
1610		let vtxo_sum = vtxos.iter().map(|v| v.amount()).sum::<Amount>();
1611
1612		if fee > vtxo_sum {
1613			bail!("offboarded amount is lower than fees. Need {fee}, got: {vtxo_sum}");
1614		}
1615
1616		let offb = OffboardRequest {
1617			amount: vtxo_sum - fee,
1618			script_pubkey: destination.clone(),
1619		};
1620
1621		Ok(RoundParticipation {
1622			inputs: vtxos.clone(),
1623			outputs: Vec::new(),
1624			offboards: vec![offb],
1625		})
1626	}
1627
1628	async fn offboard<V: VtxoRef>(
1629		&self,
1630		vtxos: impl IntoIterator<Item = V>,
1631		destination: ScriptBuf,
1632	) -> anyhow::Result<RoundStatus> {
1633		let mut participation = self.build_offboard_participation(vtxos, destination.clone())?;
1634
1635		if let Err(e) = self.add_should_refresh_vtxos(&mut participation).await {
1636			warn!("Error trying to add additional VTXOs that should be refreshed: {:#}", e);
1637		}
1638
1639		Ok(self.participate_round(participation, Some(RoundMovement::Offboard)).await?)
1640	}
1641
1642	/// Offboard all VTXOs to a given [bitcoin::Address].
1643	pub async fn offboard_all(&self, address: bitcoin::Address) -> anyhow::Result<RoundStatus> {
1644		let input_vtxos = self.spendable_vtxos()?;
1645		Ok(self.offboard(input_vtxos, address.script_pubkey()).await?)
1646	}
1647
1648	/// Offboard the given VTXOs to a given [bitcoin::Address].
1649	pub async fn offboard_vtxos<V: VtxoRef>(
1650		&self,
1651		vtxos: impl IntoIterator<Item = V>,
1652		address: bitcoin::Address,
1653	) -> anyhow::Result<RoundStatus> {
1654		let input_vtxos =  vtxos
1655			.into_iter()
1656			.map(|v| {
1657				let id = v.vtxo_id();
1658				match self.db.get_wallet_vtxo(id)? {
1659					Some(vtxo) => Ok(vtxo.vtxo),
1660					_ => bail!("cannot find requested vtxo: {}", id),
1661				}
1662			})
1663			.collect::<anyhow::Result<Vec<_>>>()?;
1664
1665		Ok(self.offboard(input_vtxos, address.script_pubkey()).await?)
1666	}
1667
1668	pub fn build_refresh_participation<V: VtxoRef>(
1669		&self,
1670		vtxos: impl IntoIterator<Item = V>,
1671	) -> anyhow::Result<Option<RoundParticipation>> {
1672		let vtxos = {
1673			let mut ret = HashMap::new();
1674			for v in vtxos {
1675				let id = v.vtxo_id();
1676				let vtxo = self.get_vtxo_by_id(id)
1677					.with_context(|| format!("vtxo with id {} not found", id))?;
1678				if !ret.insert(id, vtxo).is_none() {
1679					bail!("duplicate VTXO id: {}", id);
1680				}
1681			}
1682			ret
1683		};
1684
1685		if vtxos.is_empty() {
1686			info!("Skipping refresh since no VTXOs are provided.");
1687			return Ok(None);
1688		}
1689
1690		let total_amount = vtxos.values().map(|v| v.vtxo.amount()).sum();
1691
1692		info!("Refreshing {} VTXOs (total amount = {}).", vtxos.len(), total_amount);
1693
1694		let (user_keypair, _) = self.derive_store_next_keypair()?;
1695		let req = VtxoRequest {
1696			policy: VtxoPolicy::Pubkey(PubkeyVtxoPolicy { user_pubkey: user_keypair.public_key() }),
1697			amount: total_amount,
1698		};
1699
1700		Ok(Some(RoundParticipation {
1701			inputs: vtxos.into_values().map(|v| v.vtxo).collect(),
1702			outputs: vec![req],
1703			offboards: Vec::new(),
1704		}))
1705	}
1706
1707	/// This will refresh all provided VTXOs. Note that attempting to refresh a board VTXO which
1708	/// has not yet confirmed will result in an error.
1709	///
1710	/// Returns the [RoundId] of the round if a successful refresh occurred.
1711	/// It will return [None] if no [Vtxo] needed to be refreshed.
1712	pub async fn refresh_vtxos<V: VtxoRef>(
1713		&self,
1714		vtxos: impl IntoIterator<Item = V>,
1715	) -> anyhow::Result<Option<RoundStatus>> {
1716		let mut participation = match self.build_refresh_participation(vtxos)? {
1717			Some(participation) => participation,
1718			None => return Ok(None),
1719		};
1720
1721		if let Err(e) = self.add_should_refresh_vtxos(&mut participation).await {
1722			warn!("Error trying to add additional VTXOs that should be refreshed: {:#}", e);
1723		}
1724
1725		Ok(Some(self.participate_round(participation, Some(RoundMovement::Refresh)).await?))
1726	}
1727
1728	/// This will find all VTXOs that meets must-refresh criteria.
1729	/// Then, if there are some VTXOs to refresh, it will
1730	/// also add those that meet should-refresh criteria.
1731	pub async fn get_vtxos_to_refresh(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1732		let tip = self.chain.tip().await?;
1733		let fee_rate = self.chain.fee_rates().await.fast;
1734
1735		// Check if there is any VTXO that we must refresh
1736		let must_refresh_vtxos = self.spendable_vtxos_with(
1737			&RefreshStrategy::must_refresh(self, tip, fee_rate),
1738		)?;
1739		if must_refresh_vtxos.is_empty() {
1740			return Ok(vec![]);
1741		} else {
1742			// If we need to do a refresh, we take all the should_refresh vtxo's as well
1743			// This helps us to aggregate some VTXOs
1744			let should_refresh_vtxos = self.spendable_vtxos_with(
1745				&RefreshStrategy::should_refresh(self, tip, fee_rate),
1746			)?;
1747			Ok(should_refresh_vtxos)
1748		}
1749	}
1750
1751	/// Returns the block height at which the first VTXO will expire
1752	pub fn get_first_expiring_vtxo_blockheight(
1753		&self,
1754	) -> anyhow::Result<Option<BlockHeight>> {
1755		Ok(self.spendable_vtxos()?.iter().map(|v| v.expiry_height()).min())
1756	}
1757
1758	/// Returns the next block height at which we have a VTXO that we
1759	/// want to refresh
1760	pub fn get_next_required_refresh_blockheight(
1761		&self,
1762	) -> anyhow::Result<Option<BlockHeight>> {
1763		let first_expiry = self.get_first_expiring_vtxo_blockheight()?;
1764		Ok(first_expiry.map(|h| h.saturating_sub(self.config.vtxo_refresh_expiry_threshold)))
1765	}
1766
1767	/// Select several vtxos to cover the provided amount
1768	///
1769	/// Returns an error if amount cannot be reached
1770	///
1771	/// If `max_depth` is set, it will filter vtxos that have a depth greater than it.
1772	fn select_vtxos_to_cover(
1773		&self,
1774		amount: Amount,
1775		max_depth: Option<u16>,
1776		expiry_threshold: Option<BlockHeight>,
1777	) -> anyhow::Result<Vec<Vtxo>> {
1778		let inputs = self.spendable_vtxos()?;
1779
1780		// Iterate over all rows until the required amount is reached
1781		let mut result = Vec::new();
1782		let mut total_amount = bitcoin::Amount::ZERO;
1783		for input in inputs {
1784			if let Some(max_depth) = max_depth {
1785				if input.arkoor_depth() >= max_depth {
1786					warn!("VTXO {} reached max depth of {}, skipping it. \
1787						Please refresh your VTXO.", input.id(), max_depth,
1788					);
1789					continue;
1790				}
1791			}
1792
1793			// Check if vtxo is soon-to-expire for arkoor payments
1794			if let Some(threshold) = expiry_threshold {
1795				if input.expiry_height() < threshold {
1796					warn!("VTXO {} is expiring soon (expires at {}, threshold {}), \
1797						skipping for arkoor payment",
1798						input.id(), input.expiry_height(), threshold,
1799					);
1800					continue;
1801				}
1802			}
1803
1804			total_amount += input.amount();
1805			result.push(input.vtxo);
1806
1807			if total_amount >= amount {
1808				return Ok(result)
1809			}
1810		}
1811
1812		bail!("Insufficient money available. Needed {} but {} is available",
1813			amount, total_amount,
1814		);
1815	}
1816
1817	/// Create Arkoor VTXOs for a given destination and amount
1818	///
1819	/// Outputs cannot have more than one input, so we can create new
1820	/// arkoors for each input needed to match requested amount + one
1821	/// optional change output.
1822	async fn create_arkoor_vtxos(
1823		&self,
1824		destination_policy: VtxoPolicy,
1825		amount: Amount,
1826	) -> anyhow::Result<ArkoorCreateResult> {
1827		let mut srv = self.require_server()?;
1828		let change_pubkey = self.derive_store_next_keypair()?.0.public_key();
1829
1830		let req = VtxoRequest {
1831			amount,
1832			policy: destination_policy,
1833		};
1834
1835		// Get current height for expiry checking
1836		let tip = self.chain.tip().await?;
1837		let inputs = self.select_vtxos_to_cover(
1838			req.amount,
1839			Some(srv.info.max_arkoor_depth),
1840			Some(tip + self.config.vtxo_refresh_expiry_threshold),
1841		)?;
1842
1843		let mut secs = Vec::with_capacity(inputs.len());
1844		let mut pubs = Vec::with_capacity(inputs.len());
1845		let mut keypairs = Vec::with_capacity(inputs.len());
1846		for input in inputs.iter() {
1847			let keypair = self.get_vtxo_key(&input)?;
1848			let (s, p) = musig::nonce_pair(&keypair);
1849			secs.push(s);
1850			pubs.push(p);
1851			keypairs.push(keypair);
1852		}
1853
1854		let builder = ArkoorPackageBuilder::new(&inputs, &pubs, req, Some(change_pubkey))?;
1855
1856		let req = protos::ArkoorPackageCosignRequest {
1857			arkoors: builder.arkoors.iter().map(|a| a.into()).collect(),
1858		};
1859		let cosign_resp: Vec<_> = srv.client.request_arkoor_package_cosign(req).await?
1860			.into_inner().try_into().context("invalid server cosign response")?;
1861		ensure!(builder.verify_cosign_response(&cosign_resp),
1862			"invalid arkoor cosignature received from server",
1863		);
1864
1865		let (sent, change) = builder.build_vtxos(&cosign_resp, &keypairs, secs)?;
1866
1867		if let Some(change) = change.as_ref() {
1868			info!("Added change VTXO of {}", change.amount());
1869		}
1870
1871		Ok(ArkoorCreateResult {
1872			input: inputs,
1873			created: sent,
1874			change,
1875		})
1876	}
1877
1878	/// Validate if we can send arkoor payments to the given [ark::Address], for example an error
1879	/// will be returned if the given [ark::Address] belongs to a different server (see
1880	/// [ark::address::ArkId]).
1881	pub fn validate_arkoor_address(&self, address: &ark::Address) -> anyhow::Result<()> {
1882		let asp = self.require_server()?;
1883
1884		if !address.ark_id().is_for_server(asp.info.server_pubkey) {
1885			bail!("Ark address is for different server");
1886		}
1887
1888		// Not all policies are supported for sending arkoor
1889		match address.policy().policy_type() {
1890			VtxoPolicyKind::Pubkey => {},
1891			VtxoPolicyKind::ServerHtlcRecv | VtxoPolicyKind::ServerHtlcSend => {
1892				bail!("VTXO policy in address cannot be used for arkoor payment: {}",
1893					address.policy().policy_type(),
1894				);
1895			}
1896		}
1897
1898		if address.delivery().is_empty() {
1899			bail!("No VTXO delivery mechanism provided in address");
1900		}
1901		// We first see if we know any of the deliveries, if not, we will log
1902		// the unknown onces.
1903		// We do this in two parts because we shouldn't log unknown ones if there is one known.
1904		if !address.delivery().iter().any(|d| !d.is_unknown()) {
1905			for d in address.delivery() {
1906				if let VtxoDelivery::Unknown { delivery_type, data } = d {
1907					info!("Unknown delivery in address: type={:#x}, data={}",
1908						delivery_type, data.as_hex(),
1909					);
1910				}
1911			}
1912		}
1913
1914		Ok(())
1915	}
1916
1917	/// Makes an out-of-round payment to the given [ark::Address]. This does not require waiting for
1918	/// a round, so it should be relatively instantaneous.
1919	///
1920	/// If the [Wallet] doesn't contain a VTXO larger than the given [Amount], multiple payments
1921	/// will be chained together, resulting in the recipient receiving multiple VTXOs.
1922	///
1923	/// Note that a change [Vtxo] may be created as a result of this call. With each payment these
1924	/// will become more uneconomical to unilaterally exit, so you should eventually refresh them
1925	/// with [Wallet::refresh_vtxos] or periodically call [Wallet::maintenance_refresh].
1926	pub async fn send_arkoor_payment(
1927		&self,
1928		destination: &ark::Address,
1929		amount: Amount,
1930	) -> anyhow::Result<Vec<Vtxo>> {
1931		let mut srv = self.require_server()?;
1932
1933		self.validate_arkoor_address(&destination).context("cannot send to address")?;
1934
1935		if amount < P2TR_DUST {
1936			bail!("Sent amount must be at least {}", P2TR_DUST);
1937		}
1938
1939		let mut movement = MovementGuard::new_movement(
1940			self.movements.clone(),
1941			self.subsystem_ids[&BarkSubsystem::Arkoor],
1942			ArkoorMovement::Send.to_string(),
1943		).await?;
1944		let arkoor = self.create_arkoor_vtxos(destination.policy().clone(), amount).await?;
1945		movement.apply_update(
1946			arkoor.to_movement_update()?
1947				.sent_to([MovementDestination::new(destination.to_string(), amount)])
1948				.intended_and_effective_balance(-amount.to_signed()?)
1949		).await?;
1950
1951		let req = protos::ArkoorPackage {
1952			arkoors: arkoor.created.iter().map(|v| protos::ArkoorVtxo {
1953				pubkey: destination.policy().user_pubkey().serialize().to_vec(),
1954				vtxo: v.serialize().to_vec(),
1955			}).collect(),
1956		};
1957
1958		// TODO: Figure out how to better handle this error. Technically the payment fails but our
1959		//       funds are considered spent anyway? Maybe add the failure reason to the metadata?
1960		if let Err(e) = srv.client.post_arkoor_package_mailbox(req).await {
1961			error!("Failed to post the arkoor vtxo to the recipients mailbox: '{:#}'", e);
1962			//NB we will continue to at least not lose our own change
1963		}
1964		self.mark_vtxos_as_spent(&arkoor.input)?;
1965		if let Some(change) = arkoor.change {
1966			self.store_spendable_vtxos(&[change])?;
1967		}
1968		movement.finish(MovementStatus::Finished).await?;
1969		Ok(arkoor.created)
1970	}
1971
1972	async fn process_lightning_revocation(&self, payment: &PendingLightningSend) -> anyhow::Result<()> {
1973		let mut srv = self.require_server()?;
1974		let htlc_vtxos = payment.htlc_vtxos.clone().into_iter()
1975			.map(|v: WalletVtxo| v.vtxo).collect::<Vec<_>>();
1976
1977		info!("Processing {} HTLC VTXOs for revocation", htlc_vtxos.len());
1978
1979		let mut secs = Vec::with_capacity(htlc_vtxos.len());
1980		let mut pubs = Vec::with_capacity(htlc_vtxos.len());
1981		let mut keypairs = Vec::with_capacity(htlc_vtxos.len());
1982		for input in htlc_vtxos.iter() {
1983			let keypair = self.get_vtxo_key(&input)?;
1984			let (s, p) = musig::nonce_pair(&keypair);
1985			secs.push(s);
1986			pubs.push(p);
1987			keypairs.push(keypair);
1988		}
1989
1990		let revocation = ArkoorPackageBuilder::new_htlc_revocation(&htlc_vtxos, &pubs)?;
1991
1992		let req = protos::RevokeLightningPaymentRequest {
1993			htlc_vtxo_ids: revocation.arkoors.iter()
1994				.map(|i| i.input.id().to_bytes().to_vec())
1995				.collect(),
1996			user_nonces: revocation.arkoors.iter()
1997				.map(|i| i.user_nonce.serialize().to_vec())
1998				.collect(),
1999		};
2000		let cosign_resp: Vec<_> = srv.client.revoke_lightning_payment(req).await?
2001			.into_inner().try_into().context("invalid server cosign response")?;
2002		ensure!(revocation.verify_cosign_response(&cosign_resp),
2003			"invalid arkoor cosignature received from server",
2004		);
2005
2006		let (vtxos, _) = revocation.build_vtxos(&cosign_resp, &keypairs, secs)?;
2007		let mut revoked = Amount::ZERO;
2008		for vtxo in &vtxos {
2009			info!("Got revocation VTXO: {}: {}", vtxo.id(), vtxo.amount());
2010			revoked += vtxo.amount();
2011		}
2012
2013		let count = vtxos.len();
2014		self.movements.update_movement(
2015			payment.movement_id,
2016			MovementUpdate::new()
2017				.effective_balance(-payment.amount.to_signed()? + revoked.to_signed()?)
2018				.produced_vtxos(&vtxos)
2019		).await?;
2020		self.store_spendable_vtxos(&vtxos)?;
2021		self.mark_vtxos_as_spent(&htlc_vtxos)?;
2022		self.movements.finish_movement(payment.movement_id, MovementStatus::Failed).await?;
2023
2024		self.db.remove_pending_lightning_send(payment.invoice.payment_hash())?;
2025
2026		info!("Revoked {} HTLC VTXOs", count);
2027
2028		Ok(())
2029	}
2030
2031	/// Pays a Lightning [Invoice] using Ark VTXOs. This is also an out-of-round payment
2032	/// so the same [Wallet::send_arkoor_payment] rules apply.
2033	pub async fn pay_lightning_invoice<T>(
2034		&self,
2035		invoice: T,
2036		user_amount: Option<Amount>,
2037	) -> anyhow::Result<Preimage>
2038	where
2039		T: TryInto<Invoice>,
2040		T::Error: std::error::Error + fmt::Display + Send + Sync + 'static,
2041	{
2042		let mut srv = self.require_server()?;
2043		let properties = self.db.read_properties()?.context("Missing config")?;
2044
2045		let invoice = invoice.try_into().context("failed to parse invoice")?;
2046		if invoice.network() != properties.network {
2047			bail!("Invoice is for wrong network: {}", invoice.network());
2048		}
2049
2050		if self.db.check_recipient_exists(&invoice.to_string())? {
2051			bail!("Invoice has already been paid");
2052		}
2053
2054		invoice.check_signature()?;
2055
2056		let inv_amount = invoice.amount_msat().map(|v| Amount::from_msat_ceil(v));
2057		if let (Some(_), Some(inv)) = (user_amount, inv_amount) {
2058			bail!("Invoice has amount of {} encoded. Please omit user amount argument", inv);
2059		}
2060
2061		let amount = user_amount.or(inv_amount)
2062			.context("amount required on invoice without amount")?;
2063		if amount < P2TR_DUST {
2064			bail!("Sent amount must be at least {}", P2TR_DUST);
2065		}
2066
2067		let (change_keypair, _) = self.derive_store_next_keypair()?;
2068
2069		let inputs = self.select_vtxos_to_cover(amount, Some(srv.info.max_arkoor_depth), None)
2070			.context("Could not find enough suitable VTXOs to cover lightning payment")?;
2071
2072		let mut secs = Vec::with_capacity(inputs.len());
2073		let mut pubs = Vec::with_capacity(inputs.len());
2074		let mut keypairs = Vec::with_capacity(inputs.len());
2075		let mut input_ids = Vec::with_capacity(inputs.len());
2076		for input in inputs.iter() {
2077			let keypair = self.get_vtxo_key(&input)?;
2078			let (s, p) = musig::nonce_pair(&keypair);
2079			secs.push(s);
2080			pubs.push(p);
2081			keypairs.push(keypair);
2082			input_ids.push(input.id());
2083		}
2084
2085		let req = protos::StartLightningPaymentRequest {
2086			invoice: invoice.to_string(),
2087			user_amount_sat: user_amount.map(|a| a.to_sat()),
2088			input_vtxo_ids: input_ids.iter().map(|v| v.to_bytes().to_vec()).collect(),
2089			user_nonces: pubs.iter().map(|p| p.serialize().to_vec()).collect(),
2090			user_pubkey: change_keypair.public_key().serialize().to_vec(),
2091		};
2092
2093		let resp = srv.client.start_lightning_payment(req).await
2094			.context("htlc request failed")?.into_inner();
2095
2096		let cosign_resp = resp.sigs.into_iter().map(|i| i.try_into())
2097			.collect::<Result<Vec<_>, _>>()?;
2098		let policy = VtxoPolicy::deserialize(&resp.policy)?;
2099
2100		let pay_req = match policy {
2101			VtxoPolicy::ServerHtlcSend(policy) => {
2102				ensure!(policy.user_pubkey == change_keypair.public_key(), "user pubkey mismatch");
2103				ensure!(policy.payment_hash == invoice.payment_hash(), "payment hash mismatch");
2104				// TODO: ensure expiry is not too high? add new bark config to check against?
2105				VtxoRequest { amount: amount, policy: policy.into() }
2106			},
2107			_ => bail!("invalid policy returned from server"),
2108		};
2109
2110		let builder = ArkoorPackageBuilder::new(
2111			&inputs, &pubs, pay_req, Some(change_keypair.public_key()),
2112		)?;
2113
2114		ensure!(builder.verify_cosign_response(&cosign_resp),
2115			"invalid arkoor cosignature received from server",
2116		);
2117
2118		let (htlc_vtxos, change_vtxo) = builder.build_vtxos(&cosign_resp, &keypairs, secs)?;
2119
2120		// Validate the new vtxos. They have the same chain anchor.
2121		let mut effective_balance = Amount::ZERO;
2122		for vtxo in &htlc_vtxos {
2123			self.validate_vtxo(vtxo).await?;
2124			effective_balance += vtxo.amount();
2125		}
2126
2127		let movement_id = self.movements.new_movement(
2128			self.subsystem_ids[&BarkSubsystem::LightningSend],
2129			LightningSendMovement::Send.to_string(),
2130		).await?;
2131		self.movements.update_movement(
2132			movement_id,
2133			MovementUpdate::new()
2134				.intended_balance(-amount.to_signed()?)
2135				.effective_balance(-effective_balance.to_signed()?)
2136				.consumed_vtxos(&inputs)
2137				.sent_to([MovementDestination::new(invoice.to_string(), amount)])
2138		).await?;
2139		self.store_locked_vtxos(&htlc_vtxos, Some(movement_id))?;
2140		self.mark_vtxos_as_spent(&input_ids)?;
2141
2142		// Validate the change vtxo. It has the same chain anchor as the last input.
2143		if let Some(ref change) = change_vtxo {
2144			let last_input = inputs.last().context("no inputs provided")?;
2145			let tx = self.chain.get_tx(&last_input.chain_anchor().txid).await?;
2146			let tx = tx.with_context(|| {
2147				format!("input vtxo chain anchor not found for lightning change vtxo: {}", last_input.chain_anchor().txid)
2148			})?;
2149			change.validate(&tx).context("invalid lightning change vtxo")?;
2150			self.store_spendable_vtxos([change])?;
2151		}
2152
2153		self.movements.update_movement(
2154			movement_id,
2155			MovementUpdate::new()
2156				.produced_vtxo_if_some(change_vtxo)
2157				.metadata(LightningMovement::htlc_metadata(&htlc_vtxos)?)
2158		).await?;
2159
2160		let payment = self.db.store_new_pending_lightning_send(
2161			&invoice, &amount, &htlc_vtxos.iter().map(|v| v.id()).collect::<Vec<_>>(), movement_id,
2162		)?;
2163
2164		let req = protos::SignedLightningPaymentDetails {
2165			invoice: invoice.to_string(),
2166			htlc_vtxo_ids: htlc_vtxos.iter().map(|v| v.id().to_bytes().to_vec()).collect(),
2167			wait: true,
2168		};
2169
2170		let res = srv.client.finish_lightning_payment(req).await?.into_inner();
2171		debug!("Progress update: {}", res.progress_message);
2172		let payment_preimage = Preimage::try_from(res.payment_preimage()).ok();
2173
2174		if let Some(preimage) = payment_preimage {
2175			info!("Payment succeeded! Preimage: {}", preimage.as_hex());
2176
2177			self.db.remove_pending_lightning_send(payment.invoice.payment_hash())?;
2178			self.mark_vtxos_as_spent(&htlc_vtxos)?;
2179			self.movements.finish_movement(movement_id, MovementStatus::Finished).await?;
2180			Ok(preimage)
2181		} else {
2182			self.process_lightning_revocation(&payment).await?;
2183			bail!("No preimage, payment failed: {}", res.progress_message);
2184		}
2185	}
2186
2187	/// Checks the status of a lightning payment associated with a set of VTXOs, processes the
2188	/// payment result and optionally takes appropriate actions based on the payment outcome.
2189	///
2190	/// # Arguments
2191	///
2192	/// * `htlc_vtxos` - Slice of [WalletVtxo] objects that represent HTLC outputs involved in the
2193	///                  payment.
2194	///
2195	/// # Returns
2196	///
2197	/// Returns `Ok(Some(Preimage))` if the payment is successfully completed and a preimage is
2198	/// received.
2199	/// Returns `Ok(None)` for payments still pending, failed payments or if necessary revocation
2200	/// or exit processing occurs.
2201	/// Returns an `Err` if an error occurs during the process.
2202	///
2203	/// # Behavior
2204	///
2205	/// - Validates that all HTLC VTXOs share the same invoice, amount and policy.
2206	/// - Sends a request to the lightning payment server to check the payment status.
2207	/// - Depending on the payment status:
2208	///   - **Failed**: Revokes the associated VTXOs.
2209	///   - **Pending**: Checks if the HTLC has expired based on the tip height. If expired,
2210	///     revokes the VTXOs.
2211	///   - **Complete**: Extracts the payment preimage, logs the payment, registers movement
2212	///     in the database and returns
2213	pub async fn check_lightning_payment(&self, payment: &PendingLightningSend)
2214		-> anyhow::Result<Option<Preimage>>
2215	{
2216		let mut srv = self.require_server()?;
2217		let tip = self.chain.tip().await?;
2218
2219		let payment_hash = payment.invoice.payment_hash();
2220
2221		let policy = payment.htlc_vtxos.first().context("no vtxo provided")?.vtxo.policy();
2222		debug_assert!(payment.htlc_vtxos.iter().all(|v| v.vtxo.policy() == policy),
2223			"All lightning htlc should have the same policy",
2224		);
2225		let policy = policy.as_server_htlc_send().context("VTXO is not an HTLC send")?;
2226		if policy.payment_hash != payment_hash {
2227			bail!("Payment hash mismatch");
2228		}
2229
2230		let req = protos::CheckLightningPaymentRequest {
2231			hash: policy.payment_hash.to_vec(),
2232			wait: false,
2233		};
2234		let res = srv.client.check_lightning_payment(req).await?.into_inner();
2235
2236		let payment_status = protos::PaymentStatus::try_from(res.status)?;
2237
2238		let should_revoke = match payment_status {
2239			protos::PaymentStatus::Failed => {
2240				info!("Payment failed ({}): revoking VTXO", res.progress_message);
2241				true
2242			},
2243			protos::PaymentStatus::Pending => {
2244				trace!("Payment is still pending, HTLC expiry: {}, tip: {}",
2245					policy.htlc_expiry, tip);
2246				if tip > policy.htlc_expiry {
2247					info!("Payment is still pending, but HTLC is expired: revoking VTXO");
2248					true
2249				} else {
2250					info!("Payment is still pending and HTLC is not expired ({}): \
2251						doing nothing for now", policy.htlc_expiry,
2252					);
2253					false
2254				}
2255			},
2256			protos::PaymentStatus::Complete => {
2257				let preimage: Preimage = res.payment_preimage
2258					.context("payment completed but no preimage")?
2259					.try_into().map_err(|_| anyhow!("preimage is not 32 bytes"))?;
2260				info!("Payment is complete, preimage, {}", preimage.as_hex());
2261
2262				self.movements.finish_movement(payment.movement_id, MovementStatus::Finished).await?;
2263
2264				self.db.remove_pending_lightning_send(payment_hash)?;
2265
2266				return Ok(Some(preimage));
2267			},
2268		};
2269
2270		if should_revoke {
2271			if let Err(e) = self.process_lightning_revocation(payment).await {
2272				warn!("Failed to revoke VTXO: {}", e);
2273
2274				// if one of the htlc is about to expire, we exit all of them.
2275				// Maybe we want a different behavior here, but we have to decide whether
2276				// htlc vtxos revocation is a all or nothing process.
2277				let min_expiry = payment.htlc_vtxos.iter()
2278					.map(|v| v.vtxo.spec().expiry_height).min().unwrap();
2279
2280				if tip > min_expiry.saturating_sub(self.config().vtxo_refresh_expiry_threshold) {
2281					warn!("Some VTXO is about to expire soon, marking to exit");
2282					let vtxos = payment.htlc_vtxos
2283						.iter()
2284						.map(|v| v.vtxo.clone())
2285						.collect::<Vec<_>>();
2286					self.exit.write().await.mark_vtxos_for_exit(&vtxos).await?;
2287
2288					let exited = vtxos.iter().map(|v| v.amount()).sum::<Amount>();
2289					self.movements.update_movement(
2290						payment.movement_id,
2291						MovementUpdate::new()
2292							.effective_balance(-payment.amount.to_signed()? + exited.to_signed()?)
2293							.exited_vtxos(&vtxos)
2294					).await?;
2295					self.movements.finish_movement(
2296						payment.movement_id, MovementStatus::Failed,
2297					).await?;
2298					self.db.remove_pending_lightning_send(payment_hash)?;
2299				}
2300			}
2301		}
2302
2303		Ok(None)
2304	}
2305
2306	/// Create, store and return a [Bolt11Invoice] for offchain boarding
2307	pub async fn bolt11_invoice(&self, amount: Amount) -> anyhow::Result<Bolt11Invoice> {
2308		let mut srv = self.require_server()?;
2309		let config = self.config();
2310
2311		// User needs to enfore the following delta:
2312		// - vtxo exit delta + htlc expiry delta (to give him time to exit the vtxo before htlc expires)
2313		// - vtxo exit margin (to give him time to exit the vtxo before htlc expires)
2314		// - htlc recv claim delta (to give him time to claim the htlc before it expires)
2315		let requested_min_cltv_delta = srv.info.vtxo_exit_delta +
2316			srv.info.htlc_expiry_delta +
2317			config.vtxo_exit_margin +
2318			config.htlc_recv_claim_delta +
2319			LIGHTNING_PREPARE_CLAIM_DELTA;
2320
2321		if requested_min_cltv_delta > srv.info.max_user_invoice_cltv_delta {
2322			bail!("HTLC CLTV delta ({}) is greater than Server's max HTLC recv CLTV delta: {}",
2323				requested_min_cltv_delta,
2324				srv.info.max_user_invoice_cltv_delta,
2325			);
2326		}
2327
2328		let preimage = Preimage::random();
2329		let payment_hash = preimage.compute_payment_hash();
2330		info!("Start bolt11 board with preimage / payment hash: {} / {}",
2331			preimage.as_hex(), payment_hash.as_hex());
2332
2333		let req = protos::StartLightningReceiveRequest {
2334			payment_hash: payment_hash.to_vec(),
2335			amount_sat: amount.to_sat(),
2336			min_cltv_delta: requested_min_cltv_delta as u32,
2337		};
2338
2339		let resp = srv.client.start_lightning_receive(req).await?.into_inner();
2340		info!("Ark Server is ready to receive LN payment to invoice: {}.", resp.bolt11);
2341
2342		let invoice = Bolt11Invoice::from_str(&resp.bolt11)
2343			.context("invalid bolt11 invoice returned by Ark server")?;
2344
2345		self.db.store_lightning_receive(
2346			payment_hash,
2347			preimage,
2348			&invoice,
2349			requested_min_cltv_delta,
2350		)?;
2351
2352		Ok(invoice)
2353	}
2354
2355	/// Fetches the status of a lightning receive for the given [PaymentHash].
2356	pub fn lightning_receive_status(
2357		&self,
2358		payment: impl Into<PaymentHash>,
2359	) -> anyhow::Result<Option<LightningReceive>> {
2360		Ok(self.db.fetch_lightning_receive_by_payment_hash(payment.into())?)
2361	}
2362
2363	/// Fetches all pending lightning receives ordered from newest to oldest.
2364	pub fn pending_lightning_receives(&self) -> anyhow::Result<Vec<LightningReceive>> {
2365		Ok(self.db.get_all_pending_lightning_receives()?)
2366	}
2367
2368	pub fn pending_lightning_receive_balance(&self) -> anyhow::Result<LightningReceiveBalance> {
2369		let pending_lightning_receives = self.pending_lightning_receives()?;
2370
2371		let mut total_pending_lightning_receive = Amount::ZERO;
2372		let mut claimable_pending_lightning_receive = Amount::ZERO;
2373		for receive in pending_lightning_receives {
2374			total_pending_lightning_receive += receive.invoice.amount_milli_satoshis()
2375				.map(|a| Amount::from_msat_floor(a))
2376				.expect("ln receive invoice should have amount");
2377			if let Some(htlc_vtxos) = receive.htlc_vtxos {
2378				claimable_pending_lightning_receive += htlc_vtxos.iter().map(|v| v.amount()).sum::<Amount>();
2379			}
2380		}
2381
2382		Ok(LightningReceiveBalance {
2383			total: total_pending_lightning_receive,
2384			claimable: claimable_pending_lightning_receive,
2385		})
2386	}
2387
2388	/// Claim incoming lightning payment with the given [PaymentHash].
2389	///
2390	/// This function reveals the preimage of the lightning payment in
2391	/// exchange of getting pubkey VTXOs from HTLC ones
2392	///
2393	/// # Arguments
2394	///
2395	/// * `payment_hash` - The [PaymentHash] of the lightning payment
2396	/// to wait for.
2397	/// * `vtxos` - The list of HTLC VTXOs that were previously granted
2398	/// by the Server, with the hash lock clause matching payment hash.
2399	///
2400	/// # Returns
2401	///
2402	/// Returns an `anyhow::Result<()>`, which is:
2403	/// * `Ok(())` if the process completes successfully.
2404	/// * `Err` if an error occurs at any stage of the operation.
2405	///
2406	/// # Remarks
2407	///
2408	/// * The list of HTLC VTXOs must have the hash lock clause matching the given
2409	///   [PaymentHash].
2410	async fn claim_lightning_receive(
2411		&self,
2412		lightning_receive: &LightningReceive,
2413	) -> anyhow::Result<()> {
2414		let movement_id = lightning_receive.movement_id
2415			.context("No movement created for lightning receive")?;
2416		let mut srv = self.require_server()?;
2417
2418		// order inputs by vtxoid before we generate nonces
2419		let inputs = {
2420			let htlc_vtxos = lightning_receive.htlc_vtxos.as_ref()
2421				.context("no HTLC VTXOs set on record yet")?;
2422			let mut ret = htlc_vtxos.iter().map(|v| &v.vtxo).collect::<Vec<_>>();
2423			ret.sort_by_key(|v| v.id());
2424			ret
2425		};
2426
2427		let (keypairs, sec_nonces, pub_nonces) = inputs.iter().map(|v| {
2428			let keypair = self.get_vtxo_key(v)?;
2429			let (sec_nonce, pub_nonce) = musig::nonce_pair(&keypair);
2430			Ok((keypair, sec_nonce, pub_nonce))
2431		}).collect::<anyhow::Result<(Vec<_>, Vec<_>, Vec<_>)>>()?;
2432
2433		// Claiming arkoor against preimage
2434		let (claim_keypair, _) = self.derive_store_next_keypair()?;
2435		let receive_policy = VtxoPolicy::new_pubkey(claim_keypair.public_key());
2436
2437		let pay_req = VtxoRequest {
2438			policy: receive_policy.clone(),
2439			amount: inputs.iter().map(|v| v.amount()).sum(),
2440		};
2441		trace!("ln arkoor builder params: inputs: {:?}; user_nonces: {:?}; req: {:?}",
2442			inputs.iter().map(|v| v.id()).collect::<Vec<_>>(), pub_nonces, pay_req,
2443		);
2444		let builder = ArkoorPackageBuilder::new(
2445			inputs.iter().copied(), &pub_nonces, pay_req, None,
2446		)?;
2447
2448		info!("Claiming arkoor against payment preimage");
2449		self.db.set_preimage_revealed(lightning_receive.payment_hash)?;
2450		let resp = srv.client.claim_lightning_receive(protos::ClaimLightningReceiveRequest {
2451			payment_hash: lightning_receive.payment_hash.to_byte_array().to_vec(),
2452			payment_preimage: lightning_receive.payment_preimage.to_vec(),
2453			vtxo_policy: receive_policy.serialize(),
2454			user_pub_nonces: pub_nonces.iter().map(|n| n.serialize().to_vec()).collect(),
2455		}).await?.into_inner();
2456		let cosign_resp: Vec<_> = resp.try_into().context("invalid cosign response")?;
2457
2458		ensure!(builder.verify_cosign_response(&cosign_resp),
2459			"invalid arkoor cosignature received from server",
2460		);
2461
2462		let (outputs, change) = builder.build_vtxos(&cosign_resp, &keypairs, sec_nonces)?;
2463		if change.is_some() {
2464			bail!("shouldn't have change VTXO, this is a bug");
2465		}
2466
2467		let mut effective_balance = Amount::ZERO;
2468		for vtxo in &outputs {
2469			// TODO: bailing here results in vtxos not being registered despite preimage being revealed
2470			// should we make `srv.client.claim_lightning_receive` idempotent, so that bark can at
2471			// least retry some times before giving up and exiting?
2472			if let Err(e) = self.validate_vtxo(vtxo).await {
2473				bail!("invalid arkoor from lightning receive: {e}");
2474			}
2475			effective_balance += vtxo.amount();
2476		}
2477
2478		self.store_spendable_vtxos(&outputs)?;
2479		info!("Got arkoors from lightning: {}",
2480			outputs.iter().map(|v| v.id().to_string()).collect::<Vec<_>>().join(", ")
2481		);
2482
2483		self.movements.update_movement(
2484			movement_id,
2485			MovementUpdate::new()
2486				.effective_balance(effective_balance.to_signed()?)
2487				.produced_vtxos(&outputs)
2488		).await?;
2489		self.movements.finish_movement(
2490			movement_id, MovementStatus::Finished,
2491		).await?;
2492
2493		self.db.remove_pending_lightning_receive(lightning_receive.payment_hash)?;
2494
2495		Ok(())
2496	}
2497
2498	async fn compute_lightning_receive_anti_dos(
2499		&self,
2500		payment_hash: PaymentHash,
2501		token: Option<&str>,
2502	) -> anyhow::Result<LightningReceiveAntiDos> {
2503		Ok(if let Some(token) = token {
2504			LightningReceiveAntiDos::Token(token.to_string())
2505		} else {
2506			let challenge = LightningReceiveChallenge::new(payment_hash);
2507			// We get an existing VTXO as an anti-dos measure.
2508			let vtxo = self.select_vtxos_to_cover(Amount::ONE_SAT, None, None)
2509				.and_then(|vtxos| vtxos.into_iter().next().ok_or_else(|| anyhow!("have no spendable vtxo to prove ownership of")))?;
2510			let vtxo_keypair = self.get_vtxo_key(&vtxo).expect("owned vtxo should be in database");
2511			LightningReceiveAntiDos::InputVtxo(protos::InputVtxo {
2512				vtxo_id: vtxo.id().to_bytes().to_vec(),
2513				ownership_proof: {
2514					let sig = challenge.sign_with(vtxo.id(), vtxo_keypair);
2515					sig.serialize().to_vec()
2516				}
2517			})
2518		})
2519	}
2520
2521	/// Check for incoming lightning payment with the given [PaymentHash].
2522	///
2523	/// This function checks for an incoming lightning payment with the
2524	/// given [PaymentHash] and returns the HTLC VTXOs that are associated
2525	/// with it.
2526	///
2527	/// # Arguments
2528	///
2529	/// * `payment_hash` - The [PaymentHash] of the lightning payment
2530	/// to check for.
2531	/// * `wait` - Whether to wait for the payment to be received.
2532	/// * `token` - An optional lightning receive token used to authenticate a lightning
2533	/// receive when no spendable VTXOs are owned by this wallet.
2534	///
2535	/// # Returns
2536	///
2537	/// Returns an `anyhow::Result<Vec<WalletVtxo>>`, which is:
2538	/// * `Ok(wallet_vtxos)` if the process completes successfully, where `wallet_vtxos` is
2539	///   the list of HTLC VTXOs that are associated with the payment.
2540	/// * `Err` if an error occurs at any stage of the operation.
2541	///
2542	/// # Remarks
2543	///
2544	/// * The invoice must contain an explicit amount specified in milli-satoshis.
2545	/// * The HTLC expiry height is calculated by adding the servers' HTLC expiry delta to the
2546	///   current chain tip.
2547	/// * The payment hash must be from an invoice previously generated using
2548	///   [Wallet::bolt11_invoice].
2549	async fn check_lightning_receive(
2550		&self,
2551		payment_hash: PaymentHash,
2552		wait: bool,
2553		token: Option<&str>,
2554	) -> anyhow::Result<LightningReceive> {
2555		let mut srv = self.require_server()?;
2556		let current_height = self.chain.tip().await?;
2557
2558		let mut lightning_receive = self.db.fetch_lightning_receive_by_payment_hash(payment_hash)?
2559			.context("no lightning receive found")?;
2560
2561		// If we have already HTLC VTXOs stored, we can return them without asking the server
2562		if lightning_receive.htlc_vtxos.is_some() {
2563			return Ok(lightning_receive)
2564		}
2565
2566		info!("Waiting for payment...");
2567		let sub = srv.client.check_lightning_receive(protos::CheckLightningReceiveRequest {
2568			hash: payment_hash.to_byte_array().to_vec(), wait,
2569		}).await?.into_inner();
2570
2571		let status = protos::LightningReceiveStatus::try_from(sub.status)
2572			.with_context(|| format!("unknown payment status: {}", sub.status))?;
2573		match status {
2574			// this is the good case
2575			protos::LightningReceiveStatus::Accepted
2576				| protos::LightningReceiveStatus::HtlcsReady => {},
2577			protos::LightningReceiveStatus::Created => bail!("sender didn't initiate payment yet"),
2578			protos::LightningReceiveStatus::Settled => bail!("payment already settled"),
2579			protos::LightningReceiveStatus::Cancelled => bail!("payment was canceled"),
2580		}
2581
2582		let lightning_receive_anti_dos = match self.compute_lightning_receive_anti_dos(payment_hash, token).await {
2583			Ok(anti_dos) => Some(anti_dos),
2584			Err(e) => {
2585				warn!("Could not compute anti-dos: {e}. Trying without");
2586				None
2587			},
2588		};
2589
2590		let htlc_recv_expiry = current_height + lightning_receive.htlc_recv_cltv_delta as BlockHeight;
2591
2592		let (next_keypair, _) = self.derive_store_next_keypair()?;
2593		let req = protos::PrepareLightningReceiveClaimRequest {
2594			payment_hash: lightning_receive.payment_hash.to_vec(),
2595			user_pubkey: next_keypair.public_key().serialize().to_vec(),
2596			htlc_recv_expiry,
2597			lightning_receive_anti_dos,
2598		};
2599		let res = srv.client.prepare_lightning_receive_claim(req).await
2600			.context("error preparing lightning receive claim")?.into_inner();
2601		let vtxos = res.htlc_vtxos.into_iter()
2602			.map(|b| Vtxo::deserialize(&b))
2603			.collect::<Result<Vec<_>, _>>()
2604			.context("invalid htlc vtxos from server")?;
2605
2606		// sanity check the vtxos
2607		for vtxo in &vtxos {
2608			self.validate_vtxo(vtxo).await?;
2609
2610			if let VtxoPolicy::ServerHtlcRecv(p) = vtxo.policy() {
2611				if p.payment_hash != lightning_receive.payment_hash {
2612					bail!("invalid payment hash on HTLC VTXOs received from server: {}",
2613						p.payment_hash,
2614					);
2615				}
2616				if p.user_pubkey != next_keypair.public_key() {
2617					bail!("invalid pubkey on HTLC VTXOs received from server: {}", p.user_pubkey);
2618				}
2619				if p.htlc_expiry < htlc_recv_expiry {
2620					bail!("HTLC VTXO expiry height is less than requested: Requested {}, received {}", htlc_recv_expiry, p.htlc_expiry);
2621				}
2622			} else {
2623				bail!("invalid HTLC VTXO policy: {:?}", vtxo.policy());
2624			}
2625		}
2626
2627		// check sum match invoice amount
2628		let invoice_amount = lightning_receive.invoice.amount_milli_satoshis().map(|a| Amount::from_msat_floor(a))
2629			.expect("ln receive invoice should have amount");
2630		let htlc_amount = vtxos.iter().map(|v| v.amount()).sum::<Amount>();
2631		ensure!(vtxos.iter().map(|v| v.amount()).sum::<Amount>() >= invoice_amount,
2632			"Server didn't return enough VTXOs to cover invoice amount"
2633		);
2634
2635		let movement_id = if let Some(movement_id) = lightning_receive.movement_id {
2636			movement_id
2637		} else {
2638			self.movements.new_movement(
2639				self.subsystem_ids[&BarkSubsystem::LightningReceive],
2640				LightningReceiveMovement::Receive.to_string(),
2641			).await?
2642		};
2643		self.store_locked_vtxos(&vtxos, Some(movement_id))?;
2644
2645		let vtxo_ids = vtxos.iter().map(|v| v.id()).collect::<Vec<_>>();
2646		self.db.update_lightning_receive(payment_hash, &vtxo_ids, movement_id)?;
2647
2648		self.movements.update_movement(
2649			movement_id,
2650			MovementUpdate::new()
2651				.intended_balance(invoice_amount.to_signed()?)
2652				.effective_balance(htlc_amount.to_signed()?)
2653				.metadata(LightningMovement::htlc_metadata(&vtxos)?)
2654				.received_on(
2655					[MovementDestination::new(lightning_receive.invoice.to_string(), htlc_amount)],
2656				),
2657		).await?;
2658
2659		let vtxos = vtxos
2660			.into_iter()
2661			.map(|v| self.db
2662				.get_wallet_vtxo(v.id())
2663				.and_then(|op| op.context("Failed to get wallet VTXO for lightning receive"))
2664			).collect::<Result<Vec<_>, _>>()?;
2665
2666		lightning_receive.htlc_vtxos = Some(vtxos);
2667		lightning_receive.movement_id = Some(movement_id);
2668
2669		Ok(lightning_receive)
2670	}
2671
2672	/// Check and claim a Lightning receive
2673	///
2674	/// This function checks for an incoming lightning payment with the given [PaymentHash]
2675	/// and then claims the payment using returned HTLC VTXOs.
2676	///
2677	/// # Arguments
2678	///
2679	/// * `payment_hash` - The [PaymentHash] of the lightning payment
2680	/// to check for.
2681	/// * `wait` - Whether to wait for the payment to be received.
2682	/// * `token` - An optional lightning receive token used to authenticate a lightning
2683	/// receive when no spendable VTXOs are owned by this wallet.
2684	///
2685	/// # Returns
2686	///
2687	/// Returns an `anyhow::Result<()>`, which is:
2688	/// * `Ok(())` if the process completes successfully.
2689	/// * `Err` if an error occurs at any stage of the operation.
2690	///
2691	/// # Remarks
2692	///
2693	/// * The payment hash must be from an invoice previously generated using
2694	///   [Wallet::bolt11_invoice].
2695	pub async fn try_claim_lightning_receive(
2696		&self,
2697		payment_hash: PaymentHash,
2698		wait: bool,
2699		token: Option<&str>,
2700	) -> anyhow::Result<()> {
2701		let receive = self.check_lightning_receive(payment_hash, wait, token).await?;
2702		self.claim_lightning_receive(&receive).await
2703	}
2704
2705	/// Check and claim all opened Lightning receive
2706	///
2707	/// This function fetches all opened lightning receives and then
2708	/// concurrently tries to check and claim them
2709	///
2710	/// # Arguments
2711	///
2712	/// * `wait` - Whether to wait for each payment to be received.
2713	///
2714	/// # Returns
2715	///
2716	/// Returns an `anyhow::Result<()>`, which is:
2717	/// * `Ok(())` if the process completes successfully.
2718	/// * `Err` if an error occurs at any stage of the operation.
2719	pub async fn try_claim_all_lightning_receives(&self, wait: bool) -> anyhow::Result<()> {
2720		// Asynchronously attempts to claim all pending receive by converting the list into a stream
2721		tokio_stream::iter(self.pending_lightning_receives()?)
2722			.for_each_concurrent(3, |rcv| async move {
2723				if let Err(e) = self.try_claim_lightning_receive(rcv.invoice.into(), wait, None).await {
2724					error!("Error claiming lightning receive: {}", e);
2725				}
2726			}).await;
2727
2728		Ok(())
2729	}
2730
2731	/// Claim all pending lightning receives
2732	///
2733	/// This is different from [Wallet::try_claim_all_lightning_receives] in
2734	/// that it only affect pending lightning receives whose HTLC VTXOs
2735	/// have already been issued.
2736	///
2737	/// Note:
2738	///   - If an HTLC VTXO cannot be claimed and the htlc expiry is too close,
2739	///   it will either mark the htlc as cancelled (preimage not revealed) or
2740	///   mark the vtxo for exit (preimage revealed).
2741	async fn claim_all_pending_htlc_receives(&self) -> anyhow::Result<()> {
2742		let srv = self.require_server()?;
2743		let tip = self.chain.tip().await?;
2744		let lightning_receives = self.db.get_all_pending_lightning_receives()?;
2745		info!("Syncing {} pending lightning receives", lightning_receives.len());
2746
2747		for lightning_receive in lightning_receives {
2748			let (vtxos, movement_id) = match (
2749				&lightning_receive.htlc_vtxos, lightning_receive.movement_id
2750			) {
2751				(Some(vtxos), Some(movement_id)) => (vtxos, movement_id),
2752				(Some(_), None) => {
2753					error!("Movement missing for lightning receive: {}", lightning_receive.payment_hash);
2754					continue;
2755				}
2756				(None, _) => continue,
2757			};
2758
2759			if let Err(e) = self.claim_lightning_receive(&lightning_receive).await {
2760				error!("Failed to claim pubkey vtxo from htlc vtxo: {}", e);
2761
2762				let first_vtxo = &vtxos.first().unwrap().vtxo;
2763				debug_assert!(vtxos.iter().all(|v| {
2764					v.vtxo.policy() == first_vtxo.policy() && v.vtxo.exit_delta() == first_vtxo.exit_delta()
2765				}), "all htlc vtxos for the same payment hash should have the same policy and exit delta");
2766
2767				let vtxo_htlc_expiry = first_vtxo.policy().as_server_htlc_recv()
2768					.expect("only server htlc recv vtxos can be pending lightning recv").htlc_expiry;
2769
2770				let safe_exit_margin = first_vtxo.exit_delta() +
2771					srv.info.htlc_expiry_delta +
2772					self.config.vtxo_exit_margin;
2773
2774				if tip > vtxo_htlc_expiry.saturating_sub(safe_exit_margin as BlockHeight) {
2775					if lightning_receive.preimage_revealed_at.is_some() {
2776						warn!("HTLC-recv VTXOs are about to expire and preimage has been disclosed, must exit");
2777						self.exit.write().await.mark_vtxos_for_exit(
2778							&vtxos.iter().map(|v| v.vtxo.clone()).collect::<Vec<_>>(),
2779						).await?;
2780						self.movements.update_movement(
2781							movement_id,
2782							MovementUpdate::new()
2783								.exited_vtxos(vtxos)
2784						).await?;
2785						self.movements.finish_movement(movement_id, MovementStatus::Failed).await?;
2786					} else {
2787						warn!("HTLC-recv VTXOs are about to expire, but preimage has not been disclosed yet, mark htlc as cancelled");
2788						self.mark_vtxos_as_spent(vtxos)?;
2789						self.movements.update_movement(
2790							movement_id,
2791							MovementUpdate::new()
2792								.effective_balance(SignedAmount::ZERO)
2793						).await?;
2794						self.movements.finish_movement(
2795							movement_id, MovementStatus::Cancelled,
2796						).await?;
2797					}
2798				}
2799			}
2800		}
2801
2802		Ok(())
2803	}
2804
2805	/// Same as [Wallet::pay_lightning_invoice] but instead it pays a [LightningAddress].
2806	pub async fn pay_lightning_address(
2807		&self,
2808		addr: &LightningAddress,
2809		amount: Amount,
2810		comment: Option<&str>,
2811	) -> anyhow::Result<(Bolt11Invoice, Preimage)> {
2812		let invoice = lnurl::lnaddr_invoice(addr, amount, comment).await
2813			.context("lightning address error")?;
2814		info!("Attempting to pay invoice {}", invoice);
2815		let preimage = self.pay_lightning_invoice(invoice.clone(), None).await
2816			.context("bolt11 payment error")?;
2817		Ok((invoice, preimage))
2818	}
2819
2820	/// Attempts to pay the given BOLT12 [Offer] using offchain funds.
2821	pub async fn pay_lightning_offer(
2822		&self,
2823		offer: Offer,
2824		amount: Option<Amount>,
2825	) -> anyhow::Result<(Bolt12Invoice, Preimage)> {
2826		let mut srv = self.require_server()?;
2827
2828		let offer_bytes = {
2829			let mut bytes = Vec::new();
2830			offer.write(&mut bytes).unwrap();
2831			bytes
2832		};
2833
2834		let req = protos::FetchBolt12InvoiceRequest {
2835			offer: offer_bytes,
2836			amount_sat: amount.map(|a| a.to_sat()),
2837		};
2838
2839		let resp = srv.client.fetch_bolt12_invoice(req).await?.into_inner();
2840
2841		let invoice = Bolt12Invoice::try_from(resp.invoice)
2842			.map_err(|_| anyhow::anyhow!("invalid invoice"))?;
2843
2844		invoice.validate_issuance(offer)?;
2845
2846		let preimage = self.pay_lightning_invoice(invoice.clone(), None).await
2847			.context("bolt11 payment error")?;
2848		Ok((invoice, preimage))
2849	}
2850
2851	pub fn build_round_onchain_payment_participation(
2852		&self,
2853		addr: bitcoin::Address,
2854		amount: Amount,
2855	) -> anyhow::Result<RoundParticipation> {
2856		let srv = self.require_server()?;
2857
2858		let offb = OffboardRequest {
2859			script_pubkey: addr.script_pubkey(),
2860			amount: amount,
2861		};
2862		let required_amount = offb.amount + offb.fee(srv.info.offboard_feerate)?;
2863
2864		let inputs = self.select_vtxos_to_cover(required_amount, None, None)?;
2865
2866		let change = {
2867			let input_sum = inputs.iter().map(|v| v.amount()).sum::<Amount>();
2868			if input_sum < offb.amount {
2869				bail!("Your balance is too low. Needed: {}, available: {}",
2870					required_amount, self.balance()?.spendable,
2871				);
2872			} else if input_sum <= required_amount + P2TR_DUST {
2873				info!("No change, emptying wallet.");
2874				None
2875			} else {
2876				let change_amount = input_sum - required_amount;
2877				let (change_keypair, _) = self.derive_store_next_keypair()?;
2878				info!("Adding change vtxo for {}", change_amount);
2879				Some(VtxoRequest {
2880					amount: change_amount,
2881					policy: VtxoPolicy::new_pubkey(change_keypair.public_key()),
2882				})
2883			}
2884		};
2885
2886		Ok(RoundParticipation {
2887			inputs: inputs,
2888			outputs: change.into_iter().collect(),
2889			offboards: vec![offb],
2890		})
2891	}
2892
2893	/// Sends the given [Amount] to an onchain [bitcoin::Address]. This is an in-round operation
2894	/// which may take a long time to perform.
2895	pub async fn send_round_onchain_payment(
2896		&self,
2897		addr: bitcoin::Address,
2898		amount: Amount,
2899	) -> anyhow::Result<RoundStatus> {
2900		let mut participation = self.build_round_onchain_payment_participation(addr, amount)?;
2901
2902		if let Err(e) = self.add_should_refresh_vtxos(&mut participation).await {
2903			warn!("Error trying to add additional VTXOs that should be refreshed: {:#}", e);
2904		}
2905
2906		self.participate_round(participation, Some(RoundMovement::SendOnchain)).await
2907	}
2908
2909	/// Starts a daemon for the wallet, for more information see [Daemon].
2910	///
2911	/// Note:
2912	/// - This function doesn't check if a daemon is already running,
2913	/// so it's possible to start multiple daemons by mistake.
2914	pub async fn run_daemon(
2915		self: &Arc<Self>,
2916		shutdown: CancellationToken,
2917		onchain: Arc<RwLock<dyn ExitUnilaterally>>,
2918	) -> anyhow::Result<()> {
2919		let daemon = Daemon::new(shutdown, self.clone(), onchain)?;
2920
2921		tokio::spawn(async move {
2922			let _ = daemon.run().await;
2923		});
2924
2925		Ok(())
2926	}
2927}
2928
2929#[cfg(test)]
2930mod test {
2931	use super::*;
2932
2933	#[allow(unused)] // just exists for compile check
2934	async fn pay_lightning_invoice_argument() {
2935		//! Check the different possible argument for pay_lightning_invoice
2936
2937		let db = Arc::new(SqliteClient::open("").unwrap());
2938		let w = Wallet::open(
2939			&"".parse().unwrap(), db, Config::network_default(Network::Regtest),
2940		).await.unwrap();
2941
2942		let bolt11 = Bolt11Invoice::from_str("").unwrap();
2943		w.pay_lightning_invoice(bolt11, None).await.unwrap();
2944
2945		let bolt12 = Bolt12Invoice::from_str("").unwrap();
2946		w.pay_lightning_invoice(bolt12, None).await.unwrap();
2947
2948		let string = format!("lnbc1..");
2949		w.pay_lightning_invoice(string, None).await.unwrap();
2950
2951		let strr = "lnbc1..";
2952		w.pay_lightning_invoice(strr, None).await.unwrap();
2953
2954		let invoice = Invoice::Bolt11("".parse().unwrap());
2955		w.pay_lightning_invoice(invoice, None).await.unwrap();
2956	}
2957}