bark/movement/
mod.rs

1
2
3pub mod error;
4pub mod manager;
5pub mod update;
6mod payment_method;
7
8pub use self::payment_method::PaymentMethod;
9
10use std::collections::HashMap;
11use std::fmt;
12use std::str::FromStr;
13
14use bitcoin::{Amount, SignedAmount};
15use chrono::DateTime;
16use lightning::offers::offer::Offer;
17use lnurllib::lightning_address::LightningAddress;
18use serde::{Deserialize, Serialize};
19
20use ark::VtxoId;
21use ark::lightning::Invoice;
22
23const MOVEMENT_PENDING: &'static str = "pending";
24const MOVEMENT_SUCCESSFUL: &'static str = "successful";
25const MOVEMENT_FAILED: &'static str = "failed";
26const MOVEMENT_CANCELED: &'static str = "canceled";
27
28/// Describes an attempted movement of offchain funds within the [Wallet](crate::Wallet).
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct Movement {
31	/// The internal ID of the movement.
32	pub id: MovementId,
33	/// The status of the movement.
34	pub status: MovementStatus,
35	/// Contains information about the subsystem that created the movement as well as the purpose
36	/// of the movement.
37	pub subsystem: MovementSubsystem,
38	/// Miscellaneous metadata for the movement. This is JSON containing arbitrary information as
39	/// defined by the subsystem that created the movement.
40	pub metadata: HashMap<String, serde_json::Value>,
41	/// How much the movement was expected to increase or decrease the balance by. This is always an
42	/// estimate and often discounts any applicable fees.
43	#[serde(with = "bitcoin::amount::serde::as_sat")]
44	pub intended_balance: SignedAmount,
45	/// How much the wallet balance actually changed by. Positive numbers indicate an increase and
46	/// negative numbers indicate a decrease. This is often inclusive of applicable fees, and it
47	/// should be the most accurate number.
48	#[serde(with = "bitcoin::amount::serde::as_sat")]
49	pub effective_balance: SignedAmount,
50	/// How much the movement cost the user in offchain fees. If there are applicable onchain fees
51	/// they will not be included in this value but, depending on the subsystem, could be found in
52	/// the metadata.
53	#[serde(with = "bitcoin::amount::serde::as_sat")]
54	pub offchain_fee: Amount,
55	/// A list of external recipients that received funds from this movement.
56	pub sent_to: Vec<MovementDestination>,
57	/// Describes the means by which the wallet received funds in this movement. This could include
58	/// BOLT11 invoices or other useful data.
59	pub received_on: Vec<MovementDestination>,
60	/// A list of [Vtxo](ark::Vtxo) IDs that were consumed by this movement and are either locked or
61	/// unavailable.
62	pub input_vtxos: Vec<VtxoId>,
63	/// A list of IDs for new VTXOs that were produced as a result of this movement. Often change
64	/// VTXOs will be found here for outbound actions unless this was an inbound action.
65	pub output_vtxos: Vec<VtxoId>,
66	/// A list of IDs for VTXOs that were marked for unilateral exit as a result of this movement.
67	/// This could happen for many reasons, e.g. an unsuccessful lightning payment which can't be
68	/// revoked but is about to expire. VTXOs listed here will result in a reduction of spendable
69	/// balance due to the VTXOs being managed by the [crate::Exit] system.
70	pub exited_vtxos: Vec<VtxoId>,
71	/// Contains the times at which the movement was created, updated and completed.
72	pub time: MovementTimestamp,
73}
74
75impl Movement {
76	pub fn new(
77		id: MovementId,
78		status: MovementStatus,
79		subsystem: &MovementSubsystem,
80		time: DateTime<chrono::Local>,
81	) -> Self {
82		Self {
83			id,
84			status,
85			subsystem: subsystem.clone(),
86			time: MovementTimestamp {
87				created_at: time,
88				updated_at: time,
89				completed_at: None,
90			},
91			metadata: HashMap::new(),
92			intended_balance: SignedAmount::ZERO,
93			effective_balance: SignedAmount::ZERO,
94			offchain_fee: Amount::ZERO,
95			sent_to: vec![],
96			received_on: vec![],
97			input_vtxos: vec![],
98			output_vtxos: vec![],
99			exited_vtxos: vec![],
100		}
101	}
102}
103
104/// A unique identifier for a movement.
105#[derive(Clone, Copy, Eq, Hash, PartialEq, Deserialize, Serialize)]
106pub struct MovementId(pub u32);
107
108impl MovementId {
109	pub fn new(id: u32) -> Self {
110		Self(id)
111	}
112
113	pub fn to_bytes(&self) -> [u8; 4] {
114		self.0.to_be_bytes()
115	}
116}
117
118impl fmt::Display for MovementId {
119	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120		fmt::Display::fmt(&self.0, f)
121	}
122}
123
124impl fmt::Debug for MovementId {
125	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126		fmt::Display::fmt(&self, f)
127	}
128}
129
130/// Represents the current status of a [Movement]. It's important to note that each status can
131/// result in fund changes. As an example, a lightning payment could fail but this will still result
132/// in a change of VTXOs. You can't assume that [MovementStatus::Failed] means that user funds
133/// didn't change.
134#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
135pub enum MovementStatus {
136	/// The default status of a new [Movement]. Should be treated as in-progress.
137	Pending,
138	/// The [Movement] has completed with changes. Note; this does not necessarily mean the [Movement]
139	/// completed successfully, e.g., VTXOs may be consumed and new ones produced.
140	Successful,
141	/// The [Movement] failed to complete due to an error. This should result in changes in user
142	/// funds.
143	Failed,
144	/// A [Movement] was canceled, either by the protocol (e.g., lightning payments) or by the
145	/// user.
146	Canceled,
147}
148
149impl MovementStatus {
150	/// Returns the canonical stable string for this status.
151	///
152	/// The returned value is intended for persistence and interoperability.
153	/// Use [`MovementStatus::from_str`] to parse it back.
154	pub fn as_str(&self) -> &'static str {
155		match self {
156			Self::Pending => MOVEMENT_PENDING,
157			Self::Successful => MOVEMENT_SUCCESSFUL,
158			Self::Failed => MOVEMENT_FAILED,
159			Self::Canceled => MOVEMENT_CANCELED,
160		}
161	}
162}
163
164impl fmt::Display for MovementStatus {
165	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166		f.write_str(self.as_str())
167	}
168}
169
170impl fmt::Debug for MovementStatus {
171	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172		fmt::Display::fmt(&self, f)
173	}
174}
175
176impl FromStr for MovementStatus {
177	type Err = anyhow::Error;
178
179	/// Formats the kind as its canonical string (same as [`MovementStatus::as_str`]).
180	fn from_str(s: &str) -> Result<Self, Self::Err> {
181		match s {
182			MOVEMENT_PENDING => Ok(MovementStatus::Pending),
183			MOVEMENT_SUCCESSFUL => Ok(MovementStatus::Successful),
184			MOVEMENT_FAILED => Ok(MovementStatus::Failed),
185			MOVEMENT_CANCELED => Ok(MovementStatus::Canceled),
186			_ => bail!("Invalid MovementStatus: {}", s),
187		}
188	}
189}
190
191impl Serialize for MovementStatus {
192	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
193	where
194		S: serde::Serializer,
195	{
196		serializer.serialize_str(self.as_str())
197	}
198}
199
200impl<'de> Deserialize<'de> for MovementStatus {
201	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
202	where
203		D: serde::Deserializer<'de>,
204	{
205		let s = String::deserialize(deserializer)?;
206		MovementStatus::from_str(&s).map_err(serde::de::Error::custom)
207	}
208}
209
210/// Describes a recipient of a movement. This could either be an external recipient in send actions
211/// or it could be the bark wallet itself.
212#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
213pub struct MovementDestination {
214	/// An address, invoice or any other identifier to distinguish the recipient.
215	pub destination: PaymentMethod,
216	/// How many sats the recipient received.
217	#[serde(with = "bitcoin::amount::serde::as_sat")]
218	pub amount: Amount,
219}
220
221impl MovementDestination {
222	pub fn new(payment_method: PaymentMethod, amount: Amount) -> Self {
223		Self { destination: payment_method, amount }
224	}
225
226	pub fn ark(address: ark::Address, amount: Amount) -> Self {
227		Self::new(address.into(), amount)
228	}
229
230	pub fn bitcoin(address: bitcoin::Address, amount: Amount) -> Self {
231		Self::new(address.into(), amount)
232	}
233
234	pub fn invoice(invoice: Invoice, amount: Amount) -> Self {
235		Self::new(invoice.into(), amount)
236	}
237
238	pub fn offer(offer: Offer, amount: Amount) -> Self {
239		Self::new(offer.into(), amount)
240	}
241
242	pub fn lightning_address(address: LightningAddress, amount: Amount) -> Self {
243		Self::new(address.into(), amount)
244	}
245
246	pub fn custom(destination: String, amount: Amount) -> Self {
247		Self::new(PaymentMethod::Custom(destination), amount)
248	}
249}
250
251/// Contains information about the subsystem that created the movement as well as the purpose
252/// of the movement.
253#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
254pub struct MovementSubsystem {
255	/// The name of the subsystem that created and manages the movement.
256	pub name: String,
257	/// The action responsible for registering the movement.
258	pub kind: String,
259}
260
261/// Contains the times at which the movement was created, updated and completed.
262#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
263pub struct MovementTimestamp {
264	/// When the movement was first created.
265	pub created_at: DateTime<chrono::Local>,
266	/// When the movement was last updated.
267	pub updated_at: DateTime<chrono::Local>,
268	/// The action responsible for registering the movement.
269	pub completed_at: Option<DateTime<chrono::Local>>,
270}