Skip to main content

bark_json/
primitives.rs

1
2use std::ops::Deref;
3use std::sync::Arc;
4
5use bark::actions::WalletActionId;
6use bitcoin::{Amount, OutPoint, SignedAmount, Transaction, Txid};
7use bitcoin::secp256k1::PublicKey;
8#[cfg(feature = "utoipa")]
9use utoipa::ToSchema;
10
11use ark::{Vtxo, VtxoId};
12use ark::vtxo::{Full, VtxoPolicyKind};
13use bark::movement::MovementId;
14use bark::vtxo::VtxoState;
15use bitcoin_ext::{BlockDelta, BlockHeight};
16
17/// Reference to a block in the blockchain.
18///
19/// Contains the block height and hash. Serializes as an object with `height` and `hash` fields.
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
21#[cfg_attr(feature = "utoipa", derive(ToSchema))]
22pub struct BlockRef {
23	pub height: BlockHeight,
24	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
25	pub hash: bitcoin::BlockHash,
26}
27
28impl From<bitcoin_ext::BlockRef> for BlockRef {
29	fn from(v: bitcoin_ext::BlockRef) -> Self {
30		BlockRef {
31			height: v.height,
32			hash: v.hash,
33		}
34	}
35}
36
37impl From<BlockRef> for bitcoin_ext::BlockRef {
38	fn from(v: BlockRef) -> Self {
39		bitcoin_ext::BlockRef {
40			height: v.height,
41			hash: v.hash,
42		}
43	}
44}
45
46/// Struct representing information about an Unspent Transaction Output (UTXO).
47///
48/// This structure provides details about a UTXO, which includes the outpoint (transaction ID and
49/// index), the associated amount in satoshis, and the block height at which the transaction was
50/// confirmed (if available).
51///
52/// # Serde Behavior
53///
54/// * The `amount` field is serialized and deserialized with a custom function from the `bitcoin`
55///   crate that ensures the value is interpreted as satoshis with the name `amount_sat`.
56#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
57#[cfg_attr(feature = "utoipa", derive(ToSchema))]
58pub struct UtxoInfo {
59	/// Contains the reference to the specific transaction output via transaction ID and index.
60	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
61	pub outpoint: OutPoint,
62	/// The value of the UTXO in satoshis.
63	#[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
64	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
65	pub amount: Amount,
66	/// An optional field that specifies the block height at which the transaction was confirmed. If
67	/// the transaction is unconfirmed, this value will be `None`.
68	pub confirmation_height: Option<u32>,
69}
70
71impl From<bark::UtxoInfo> for UtxoInfo {
72	fn from(v: bark::UtxoInfo) -> Self {
73		UtxoInfo {
74			outpoint: v.outpoint,
75			amount: v.amount,
76			confirmation_height: v.confirmation_height,
77		}
78	}
79}
80
81impl From<bark::onchain::Utxo> for UtxoInfo {
82
83	fn from(v: bark::onchain::Utxo) -> Self {
84		match v {
85			bark::onchain::Utxo::Local(o) => UtxoInfo {
86				outpoint: o.outpoint,
87				amount: o.amount,
88				confirmation_height: o.confirmation_height,
89			},
90			bark::onchain::Utxo::Exit(e) => UtxoInfo {
91				outpoint: e.vtxo.point(),
92				amount: e.vtxo.amount(),
93				confirmation_height: Some(e.height),
94			},
95		}
96	}
97}
98
99/// Information about a single VTXO (Virtual Transaction Output).
100///
101/// A VTXO is a chain of off-chain, pre-signed transactions rooted in an
102/// on-chain output. It represents spendable bitcoin on Ark.
103#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
104#[cfg_attr(feature = "utoipa", derive(ToSchema))]
105pub struct VtxoInfo {
106	/// Unique identifier for this VTXO, formatted as `txid:vout`.
107	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
108	pub id: VtxoId,
109	/// The value of this VTXO in sats.
110	#[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
111	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
112	pub amount: Amount,
113	/// The spending policy that governs this VTXO.
114	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
115	pub policy_type: VtxoPolicyKind,
116	/// The owner's public key. Only the holder of the corresponding
117	/// private key can spend this VTXO.
118	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
119	pub user_pubkey: PublicKey,
120	/// The Ark server's public key used to co-sign transactions
121	/// involving this VTXO.
122	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
123	pub server_pubkey: PublicKey,
124	/// The block height at which this VTXO expires. After expiry, the
125	/// server can reclaim the sats. Refresh before expiry to receive
126	/// new VTXOs, or exit to move them on-chain.
127	pub expiry_height: BlockHeight,
128	/// The relative timelock, in blocks, that must elapse before the
129	/// final on-chain claim in an emergency exit.
130	pub exit_delta: BlockDelta,
131	/// The on-chain outpoint that roots this VTXO, formatted as
132	/// `txid:vout`. Typically an output of a round transaction or a
133	/// board transaction.
134	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
135	pub chain_anchor: OutPoint,
136	/// The number of off-chain transactions in this VTXO. Each must
137	/// be broadcast and confirmed on-chain in sequence during an
138	/// emergency exit.
139	pub exit_depth: Option<u16>,
140}
141
142impl<'a> From<&'a Vtxo<Full>> for VtxoInfo {
143	fn from(v: &'a Vtxo<Full>) -> VtxoInfo {
144		VtxoInfo {
145			id: v.id(),
146			amount: v.amount(),
147			policy_type: v.policy().policy_type(),
148			user_pubkey: v.user_pubkey(),
149			server_pubkey: v.server_pubkey(),
150			expiry_height: v.expiry_height(),
151			exit_delta: v.exit_delta(),
152			chain_anchor: v.chain_anchor(),
153			exit_depth: Some(v.exit_depth()),
154		}
155	}
156}
157
158impl From<Vtxo<Full>> for VtxoInfo {
159	fn from(v: Vtxo<Full>) -> VtxoInfo {
160		VtxoInfo::from(&v)
161	}
162}
163
164/// A VTXO together with its current wallet state.
165#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
166#[cfg_attr(feature = "utoipa", derive(ToSchema))]
167pub struct WalletVtxoInfo {
168	/// The VTXO details.
169	#[serde(flatten)]
170	pub vtxo: VtxoInfo,
171	/// The current state of this VTXO in the wallet.
172	pub state: VtxoStateInfo,
173}
174
175impl<'a> From<&'a bark::WalletVtxo> for WalletVtxoInfo {
176	fn from(v: &'a bark::WalletVtxo) -> Self {
177		WalletVtxoInfo {
178			vtxo: VtxoInfo {
179				id: v.id(),
180				amount: v.amount(),
181				policy_type: v.policy().policy_type(),
182				user_pubkey: v.user_pubkey(),
183				server_pubkey: v.server_pubkey(),
184				expiry_height: v.expiry_height(),
185				exit_delta: v.exit_delta(),
186				chain_anchor: v.chain_anchor(),
187				exit_depth: Some(v.exit_depth),
188			},
189			state: VtxoStateInfo::from(&v.state),
190		}
191	}
192}
193
194/// Hex-encoded serialized VTXO.
195///
196/// Serializes as a plain hex string. Can be passed to
197/// `POST /wallet/import-vtxo` to re-import this VTXO.
198#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
199#[cfg_attr(feature = "utoipa", derive(ToSchema))]
200pub struct EncodedVtxo(pub String);
201
202impl Deref for WalletVtxoInfo {
203	type Target = VtxoInfo;
204
205	fn deref(&self) -> &Self::Target {
206		&self.vtxo
207	}
208}
209
210/// The current state of a VTXO in the wallet.
211#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
212#[cfg_attr(feature = "utoipa", derive(ToSchema))]
213#[serde(tag = "type", rename_all = "kebab-case")]
214pub enum VtxoStateInfo {
215	/// The VTXO can be spent immediately.
216	Spendable,
217	/// The VTXO has already been spent.
218	Spent,
219	/// The VTXO has been moved on-chain via a unilateral exit and is no longer
220	/// usable in the protocol.
221	Exited,
222	/// The VTXO is locked by an in-progress movement (e.g. a pending
223	/// round or Lightning payment).
224	Locked {
225		/// The movement that locked this VTXO, if any.
226		#[serde(skip_serializing_if = "Option::is_none")]
227		#[cfg_attr(feature = "utoipa", schema(value_type = u32))]
228		movement_id: Option<MovementId>,
229		/// The action that locked this VTXO, if any.
230		#[serde(skip_serializing_if = "Option::is_none")]
231		#[cfg_attr(feature = "utoipa", schema(value_type = String))]
232		action_id: Option<WalletActionId>,
233	},
234}
235
236impl<'a> From<&'a VtxoState> for VtxoStateInfo {
237	fn from(state: &'a VtxoState) -> Self {
238		match state {
239			VtxoState::Spendable => VtxoStateInfo::Spendable,
240			VtxoState::Spent => VtxoStateInfo::Spent,
241			VtxoState::Exited => VtxoStateInfo::Exited,
242			VtxoState::Locked { holder } => {
243				match holder {
244					Some(bark::vtxo::VtxoLockHolder::Movement { id }) => {
245						VtxoStateInfo::Locked { movement_id: Some(*id), action_id: None }
246					},
247					Some(bark::vtxo::VtxoLockHolder::Action { id }) => {
248						VtxoStateInfo::Locked { movement_id: None, action_id: Some(id.clone()) }
249					},
250					None => VtxoStateInfo::Locked { movement_id: None, action_id: None },
251				}
252			},
253		}
254	}
255}
256
257/// An information struct used to pair the ID of a transaction with the full transaction for ease
258/// of use and readability for the user
259#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
260#[cfg_attr(feature = "utoipa", derive(ToSchema))]
261pub struct TransactionInfo {
262	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
263	pub txid: Txid,
264	#[serde(with = "bitcoin::consensus::serde::With::<bitcoin::consensus::serde::Hex>")]
265	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
266	pub tx: Transaction,
267}
268
269impl From<bark::exit::TransactionInfo> for TransactionInfo {
270	fn from(v: bark::exit::TransactionInfo) -> Self {
271		TransactionInfo { txid: v.txid, tx: v.tx }
272	}
273}
274
275impl From<Transaction> for TransactionInfo {
276	fn from(v: Transaction) -> Self {
277		TransactionInfo { txid: v.compute_txid(), tx: v }
278	}
279}
280
281impl From<Arc<Transaction>> for TransactionInfo {
282	fn from(v: Arc<Transaction>) -> Self {
283		TransactionInfo { txid: v.compute_txid(), tx: (*v).clone() }
284	}
285}
286
287/// A richer wallet-transaction summary returned by the onchain transactions endpoint.
288///
289/// Includes the raw transaction plus its fee, the wallet's net balance change, and
290/// confirmation status.
291#[derive(Clone, Debug, Deserialize, Serialize)]
292#[cfg_attr(feature = "utoipa", derive(ToSchema))]
293pub struct WalletTxInfo {
294	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
295	pub txid: Txid,
296	#[serde(with = "bitcoin::consensus::serde::With::<bitcoin::consensus::serde::Hex>")]
297	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
298	pub tx: Transaction,
299	/// Total fee paid by the transaction, when known. `None` for txs whose foreign
300	/// prevouts BDK has not indexed (e.g. inbound payments observed via the
301	/// bitcoind-rpc sync path; esplora sync always populates prevouts).
302	#[serde(rename = "onchain_fee_sat", default, with = "bitcoin::amount::serde::as_sat::opt", skip_serializing_if = "Option::is_none")]
303	#[cfg_attr(feature = "utoipa", schema(value_type = Option<u64>))]
304	pub onchain_fees: Option<Amount>,
305	/// Net change to the wallet's balance: `received - sent` over wallet-owned outputs.
306	/// Positive for inbound, negative for outbound, zero for self-spends with no net change.
307	#[serde(rename = "balance_change_sat", with = "bitcoin::amount::serde::as_sat")]
308	#[cfg_attr(feature = "utoipa", schema(value_type = i64))]
309	pub balance_change: SignedAmount,
310	/// `Some` when the transaction is mined; `None` while still in the mempool.
311	pub confirmation: Option<BlockRef>,
312	/// `true` when this tx spends a P2A fee anchor output — i.e. it is a CPFP
313	/// child bumping its parent. In bark this typically means the wallet is
314	/// fee-bumping an exit transaction.
315	pub is_cpfp: bool,
316}
317
318impl From<bark::onchain::WalletTxInfo> for WalletTxInfo {
319	fn from(v: bark::onchain::WalletTxInfo) -> Self {
320		WalletTxInfo {
321			txid: v.txid,
322			tx: (*v.tx).clone(),
323			onchain_fees: v.onchain_fees,
324			balance_change: v.balance_change,
325			confirmation: v.confirmation.map(Into::into),
326			is_cpfp: v.is_cpfp,
327		}
328	}
329}