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	/// List of the different states considered unspent
53	pub const UNSPENT_STATES: &[VtxoStateKind] = &[
54		VtxoStateKind::Spendable,
55		VtxoStateKind::Locked,
56	];
57}
58
59impl fmt::Display for VtxoStateKind {
60	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61	    f.write_str(self.as_str())
62	}
63}
64
65impl fmt::Debug for VtxoStateKind {
66	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67	    f.write_str(self.as_str())
68	}
69}
70
71/// Rich [Vtxo] state carrying additional context needed at runtime.
72#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
73#[serde(tag = "type", rename_all = "kebab-case")]
74pub enum VtxoState {
75	/// The [Vtxo] is available and can be spent in a future round.
76	Spendable,
77	/// The [Vtxo] has been consumed.
78	Spent,
79	/// The [Vtxo] is currently locked in an action.
80	Locked {
81		/// The ID of the associated [Movement](crate::movement::Movement) that locked this VTXO.
82		movement_id: Option<MovementId>,
83	},
84}
85
86impl VtxoState {
87	/// Returns the compact [VtxoStateKind] discriminator for this rich state.
88	pub fn kind(&self) -> VtxoStateKind {
89		match self {
90			VtxoState::Locked { .. } => VtxoStateKind::Locked,
91			VtxoState::Spendable => VtxoStateKind::Spendable,
92			VtxoState::Spent => VtxoStateKind::Spent,
93		}
94	}
95}
96
97/// A wallet-owned [Vtxo] paired with its current tracked state.
98#[derive(Clone, Debug, PartialEq, Eq)]
99pub struct WalletVtxo {
100	/// The underlying [Vtxo].
101	pub vtxo: Vtxo,
102	/// The current tracked state for [WalletVtxo::vtxo].
103	pub state: VtxoState,
104}
105
106impl VtxoRef for WalletVtxo {
107	fn vtxo_id(&self) -> ark::VtxoId { self.vtxo.id() }
108	fn vtxo(&self) -> Option<&Vtxo> { Some(&self.vtxo) }
109}
110
111impl<'a> VtxoRef for &'a WalletVtxo {
112	fn vtxo_id(&self) -> ark::VtxoId { self.vtxo.id() }
113	fn vtxo(&self) -> Option<&Vtxo> { Some(&self.vtxo) }
114}
115
116impl AsRef<Vtxo> for WalletVtxo {
117	fn as_ref(&self) -> &Vtxo {
118		&self.vtxo
119	}
120}
121
122impl Deref for WalletVtxo {
123	type Target = Vtxo;
124
125	fn deref(&self) -> &Vtxo {
126		&self.vtxo
127	}
128}
129
130#[cfg(test)]
131mod test {
132	use super::*;
133
134	#[test]
135	fn convert_serialize() {
136		let states = [
137			VtxoStateKind::Spendable,
138			VtxoStateKind::Spent,
139			VtxoStateKind::Locked,
140		];
141
142		assert_eq!(
143			serde_json::to_string(&states).unwrap(),
144			serde_json::to_string(&[SPENDABLE, SPENT, LOCKED]).unwrap(),
145		);
146
147		// If a compiler error occurs,
148		// This is a reminder that you should update the test above
149		match VtxoState::Spent {
150			VtxoState::Spendable => {},
151			VtxoState::Spent => {},
152			VtxoState::Locked { .. } => {},
153		}
154	}
155}