bark/onchain/mod.rs
1//! Onchain wallet integration interfaces.
2//!
3//! This module defines the traits and types that an external onchain wallet must
4//! implement to be used by the library. The goal is to let integrators plug in
5//! their own wallet implementation, so features like boarding (moving onchain funds
6//! into Ark) and unilateral exit (claiming VTXOs onchain without server cooperation)
7//! are supported.
8//!
9//! Key concepts exposed here:
10//! - [Utxo], [LocalUtxo] & [SpendableExit]: lightweight types representing wallet UTXOs and
11//! spendable exit outputs.
12//! - [OnchainWalletTrait]: unified interface covering balance, address, PSBT construction and signing,
13//! transaction lookups, and CPFP fee-bumping for unilateral exits.
14//!
15//! A reference implementation based on BDK is available behind the `onchain-bdk`
16//! cargo feature. Enable it to use the provided [OnchainWallet] implementation.
17//! You can use all features from BDK because [bdk_wallet] is re-exported.
18
19#[cfg(feature = "onchain-bdk")]
20mod bdk;
21
22#[cfg(feature = "onchain-bdk")]
23pub use bdk_wallet;
24
25pub use bitcoin_ext::cpfp::{CpfpError, MakeCpfpFees};
26
27/// BDK-backed onchain wallet implementation.
28///
29/// Available only when the `onchain-bdk` feature is enabled.
30#[cfg(feature = "onchain-bdk")]
31pub use crate::onchain::bdk::{OnchainWallet, TxBuilderExt};
32
33use std::sync::Arc;
34
35use bitcoin::{
36 Address, Amount, FeeRate, OutPoint, Psbt, Script, SignedAmount, Transaction, Txid,
37};
38
39use ark::Vtxo;
40use ark::vtxo::Full;
41use bitcoin_ext::{BlockHeight, BlockRef};
42
43use crate::chain::ChainSource;
44
45
46/// Summary of a wallet transaction produced by [OnchainWallet::list_transaction_infos].
47#[derive(Debug, Clone)]
48pub struct WalletTxInfo {
49 pub txid: Txid,
50 pub tx: Arc<Transaction>,
51 /// Total fee paid by the transaction, when computable. `None` for inbound or
52 /// collaboratively-funded txs whose foreign prevouts BDK has not indexed
53 /// (e.g. after a bitcoind-rpc sync — esplora syncs populate prevouts).
54 pub onchain_fees: Option<Amount>,
55 /// Net change to the wallet's balance: `received - sent` over wallet-owned outputs.
56 pub balance_change: SignedAmount,
57 /// `Some` if the transaction is confirmed in a block, `None` if still in the mempool.
58 pub confirmation: Option<BlockRef>,
59 /// `true` when this tx spends a P2A fee anchor — i.e. it is a CPFP child
60 /// bumping the parent that created the anchor.
61 pub is_cpfp: bool,
62}
63
64/// Represents an onchain UTXO known to the wallet.
65///
66/// This can be either:
67/// - `Local`: a standard wallet UTXO
68/// - `Exit`: a spendable exit output produced by the Ark exit mechanism
69#[derive(Debug, Clone)]
70pub enum Utxo {
71 Local(LocalUtxo),
72 Exit(SpendableExit),
73}
74
75/// A standard wallet [Utxo] owned by the local wallet implementation.
76#[derive(Debug, Clone)]
77pub struct LocalUtxo {
78 /// The outpoint referencing the UTXO.
79 pub outpoint: OutPoint,
80 /// The amount contained in the UTXO.
81 pub amount: Amount,
82 /// Optional confirmation height; `None` if unconfirmed.
83 pub confirmation_height: Option<BlockHeight>,
84}
85
86/// A spendable unilateral exit of a [Vtxo] which can be claimed onchain.
87///
88/// When exiting unilaterally, the wallet will end up with onchain outputs that correspond to
89/// previously-held VTXOs. These can be claimed and used for further spending.
90#[derive(Debug, Clone)]
91pub struct SpendableExit {
92 /// The VTXO being exited.
93 pub vtxo: Vtxo<Full>,
94 /// The block height associated with the exits' validity window.
95 pub height: BlockHeight,
96}
97
98#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
99#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
100pub trait OnchainWalletTrait: std::any::Any + Send + Sync {
101 /// Get the total balance of the wallet
102 async fn balance(&self) -> Amount;
103
104 /// Get an onchain receive address from the wallet
105 async fn address(&mut self) -> anyhow::Result<Address>;
106
107 /// Sync the wallet with the onchain network.
108 async fn sync(&mut self, chain: &ChainSource) -> anyhow::Result<()>;
109
110 /// Returns `true` if the given script pubkey belongs to the wallet's keychains.
111 async fn is_mine(&self, spk: &Script) -> anyhow::Result<bool>;
112
113 /// Register an unconfirmed transaction relevant to the wallet
114 async fn register_tx(&mut self, tx: &Transaction) -> anyhow::Result<()>;
115
116 /// Prepare a [Transaction] which will send to the given destinations
117 async fn prepare_tx(
118 &mut self,
119 destinations: &[(Address, Amount)],
120 fee_rate: FeeRate,
121 ) -> anyhow::Result<Psbt>;
122
123 /// Prepare a [Transaction] for sending all wallet funds to the given destination
124 async fn prepare_drain_tx(
125 &mut self,
126 destination: Address,
127 fee_rate: FeeRate,
128 ) -> anyhow::Result<Psbt>;
129
130 /// Consume a [Psbt] and return a fully signed [Psbt] with all witnesses filled in
131 ///
132 /// Useful when the signed [Psbt] is needed after signing, e.g. to compute fees
133 /// via [Psbt::fee] before extracting the final [Transaction].
134 ///
135 /// Wallets should apply all necessary signatures and finalize inputs according
136 /// to their internal key management and policies.
137 async fn finish_psbt(&mut self, psbt: Psbt) -> anyhow::Result<Psbt>;
138
139 /// Creates a signed Child Pays for Parent (CPFP) transaction using a Pay-to-Anchor (P2A) output
140 /// to broadcast unilateral exits and other TRUC transactions.
141 ///
142 /// For more information please see [BIP431](https://github.com/bitcoin/bips/blob/master/bip-0431.mediawiki#topologically-restricted-until-confirmation).
143 ///
144 /// # Arguments
145 ///
146 /// * `tx` - A parent `Transaction` that is guaranteed to have one P2A output which
147 /// implementations must spend so that both the parent and child transactions can be
148 /// broadcast to the network as a v3 transaction package.
149 /// * `fees` - Informs the implementation how fees should be paid by the child transaction. Note
150 /// that an effective fee rate should be calculated using the weight of both the
151 /// parent and child transactions.
152 ///
153 /// # Returns
154 ///
155 /// Returns a `Result` containing:
156 /// * `Transaction` - The signed CPFP transaction ready to be broadcasted to the network with
157 /// the given parent transaction if construction and signing were successful.
158 /// * `CpfpError` - An error indicating the reason for failure in constructing the CPFP
159 /// transaction (e.g., insufficient funds, invalid parent transaction, or
160 /// signing failure).
161 async fn make_signed_p2a_cpfp(
162 &mut self,
163 tx: &Transaction,
164 fees: MakeCpfpFees,
165 ) -> Result<Transaction, CpfpError>;
166
167 /// Persist the signed CPFP transaction so it can be rebroadcast or retrieved as needed.
168 async fn store_signed_p2a_cpfp(&mut self, tx: &Transaction) -> anyhow::Result<(), CpfpError>;
169}