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