bark/vtxo/
state.rs

1//! VTXO state tracking.
2//!
3//! This module defines the state machine used to track the lifecycle of each individual [Vtxo]
4//! managed by the wallet. A [Vtxo] can be:
5//! - created and ready to spend on Ark: [VtxoStateKind::Spendable]
6//! - owned but not usable because it is locked by subsystem: [VtxoStateKind::Locked]
7//! - consumed (no longer part of the wallet's balance): [VtxoStateKind::Spent]
8//!
9//! Two layers of state are provided:
10//! - [VtxoStateKind]: a compact, serialization-friendly discriminator intended for storage, logs,
11//!   and wire formats. It maps to stable string identifiers via `as_str()`.
12//! - [VtxoState]: A richer state that might include metadata
13//!
14//! [WalletVtxo] pairs a concrete [Vtxo] with its current [VtxoState], providing the primary
15//! representation used by persistence and higher-level wallet logic.
16
17use std::fmt;
18use std::ops::Deref;
19
20use ark::vtxo::VtxoRef;
21
22use ark::Vtxo;
23use crate::movement::MovementId;
24
25const SPENDABLE: &'static str = "Spendable";
26const LOCKED: &'static str = "Locked";
27const SPENT: &'static str = "Spent";
28
29/// A compact, serialization-friendly representation of a VTXO's state.
30///
31/// Use [VtxoState::kind] to derive it from a richer [VtxoState].
32#[derive(Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
33pub enum VtxoStateKind {
34	/// The [Vtxo] is available and can be selected as an input for a new offboard/round.
35	Spendable,
36	/// The [Vtxo] is currently locked in an action.
37	Locked,
38	/// The [Vtxo] has been consumed and is no longer part of the wallet's balance.
39	Spent,
40}
41
42impl VtxoStateKind {
43	/// Returns a stable string identifier for this state, suitable for DB rows, logs, and APIs.
44	pub fn as_str(&self) -> &str {
45		match self {
46			VtxoStateKind::Spendable => SPENDABLE,
47			VtxoStateKind::Locked => LOCKED,
48			VtxoStateKind::Spent => SPENT,
49		}
50	}
51}
52
53impl fmt::Display for VtxoStateKind {
54	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55	    f.write_str(self.as_str())
56	}
57}
58
59impl fmt::Debug for VtxoStateKind {
60	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61	    f.write_str(self.as_str())
62	}
63}
64
65lazy_static::lazy_static! {
66	pub static ref UNSPENT_STATES: Vec<VtxoStateKind> = vec![
67		VtxoStateKind::Spendable,
68		VtxoStateKind::Locked,
69	];
70}
71
72/// Rich [Vtxo] state carrying additional context needed at runtime.
73#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
74#[serde(tag = "type", rename_all = "kebab-case")]
75pub enum VtxoState {
76	/// The [Vtxo] is available and can be spent in a future round.
77	Spendable,
78	/// The [Vtxo] has been consumed.
79	Spent,
80	/// The [Vtxo] is currently locked in an action.
81	Locked {
82		/// The ID of the associated [Movement] that locked this VTXO.
83		movement_id: Option<MovementId>,
84	},
85}
86
87impl VtxoState {
88	/// Returns the compact [VtxoStateKind] discriminator for this rich state.
89	pub fn kind(&self) -> VtxoStateKind {
90		match self {
91			VtxoState::Locked { .. } => VtxoStateKind::Locked,
92			VtxoState::Spendable => VtxoStateKind::Spendable,
93			VtxoState::Spent => VtxoStateKind::Spent,
94		}
95	}
96}
97
98/// A wallet-owned [Vtxo] paired with its current tracked state.
99#[derive(Clone, Debug, PartialEq, Eq)]
100pub struct WalletVtxo {
101	/// The underlying [Vtxo].
102	pub vtxo: Vtxo,
103	/// The current tracked state for [WalletVtxo::vtxo].
104	pub state: VtxoState,
105}
106
107impl VtxoRef for WalletVtxo {
108	fn vtxo_id(&self) -> ark::VtxoId { self.vtxo.id() }
109	fn vtxo(&self) -> Option<&Vtxo> { Some(&self.vtxo) }
110}
111
112impl<'a> VtxoRef for &'a WalletVtxo {
113	fn vtxo_id(&self) -> ark::VtxoId { self.vtxo.id() }
114	fn vtxo(&self) -> Option<&Vtxo> { Some(&self.vtxo) }
115}
116
117impl Deref for WalletVtxo {
118	type Target = Vtxo;
119
120	fn deref(&self) -> &Vtxo {
121		&self.vtxo
122	}
123}
124
125#[cfg(test)]
126mod test {
127	use super::*;
128
129	#[test]
130	fn convert_serialize() {
131		let states = [
132			VtxoStateKind::Spendable,
133			VtxoStateKind::Spent,
134			VtxoStateKind::Locked,
135		];
136
137		assert_eq!(
138			serde_json::to_string(&states).unwrap(),
139			serde_json::to_string(&[SPENDABLE, SPENT, LOCKED]).unwrap(),
140		);
141
142		// If a compiler error occurs,
143		// This is a reminder that you should update the test above
144		match VtxoState::Spent {
145			VtxoState::Spendable => {},
146			VtxoState::Spent => {},
147			VtxoState::Locked { .. } => {},
148		}
149	}
150}