bark/movement/
update.rs

1use std::collections::HashMap;
2use std::hash::Hash;
3
4use bdk_core::Merge;
5use bdk_esplora::esplora_client::Amount;
6use bitcoin::SignedAmount;
7use chrono::DateTime;
8
9use ark::vtxo::VtxoRef;
10use ark::VtxoId;
11
12use crate::movement::{Movement, MovementDestination};
13
14/// Informs [Movement::apply_update] how to apply a [MovementUpdate].
15#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
16enum UpdateMethod<T> {
17	/// Combines the given values with any existing values, ensuring no duplicates are present.
18	Merge(T),
19	/// Replaces any existing values with the given value.
20	Replace(T),
21}
22
23/// A struct to allow easy updating of a [Movement]. Each field can be set individually; however,
24/// methods are available to allow construction of an update in a more expressive/declarative way.
25///
26/// Each [Option] field that is set to `None` will be ignored. The default behavior of each field is
27/// to merge existing values with the new ones unless a field is explicitly set to
28/// [UpdateMethod::Replace] or a method indicates otherwise. Duplicate [VtxoId] values will be
29/// ignored.
30///
31/// See [UpdateMethod] to understand how to control how the [MovementUpdate] is applied.
32#[derive(Debug, Clone)]
33pub struct MovementUpdate {
34	intended_balance: Option<SignedAmount>,
35	effective_balance: Option<SignedAmount>,
36	offchain_fee: Option<Amount>,
37	sent_to: Option<UpdateMethod<Vec<MovementDestination>>>,
38	received_on: Option<UpdateMethod<Vec<MovementDestination>>>,
39	consumed_vtxos: Option<UpdateMethod<Vec<VtxoId>>>,
40	produced_vtxos: Option<UpdateMethod<Vec<VtxoId>>>,
41	exited_vtxos: Option<UpdateMethod<Vec<VtxoId>>>,
42	metadata: Option<UpdateMethod<HashMap<String, serde_json::Value>>>,
43}
44
45impl MovementUpdate {
46	pub fn new() -> Self {
47		Self {
48			intended_balance: None,
49			effective_balance: None,
50			offchain_fee: None,
51			sent_to: None,
52			received_on: None,
53			consumed_vtxos: None,
54			produced_vtxos: None,
55			exited_vtxos: None,
56			metadata: None,
57		}
58	}
59
60	pub fn consumed_vtxo(self, vtxo: impl VtxoRef) -> Self {
61		self.consumed_vtxos([vtxo.vtxo_id()])
62	}
63
64	pub fn consumed_vtxo_if_some(self, vtxo: Option<impl VtxoRef>) -> Self {
65		if let Some(vtxo) = vtxo {
66			self.consumed_vtxo(vtxo)
67		} else {
68			self
69		}
70	}
71
72	pub fn consumed_vtxos(mut self, vtxos: impl IntoIterator<Item = impl VtxoRef>) -> Self {
73		let vtxos = vtxos.into_iter().map(|vtxo| vtxo.vtxo_id());
74		match &mut self.consumed_vtxos {
75			None => self.consumed_vtxos = Some(UpdateMethod::Merge(vtxos.collect())),
76			Some(vec) => vec.merge(vtxos),
77		}
78		self
79	}
80
81	pub fn effective_balance(mut self, effective: SignedAmount) -> Self {
82		self.effective_balance = Some(effective);
83		self
84	}
85
86	pub fn exited_vtxo(self, vtxo: impl VtxoRef) -> Self {
87		self.exited_vtxos([vtxo.vtxo_id()])
88	}
89
90	pub fn exited_vtxo_if_some(self, vtxo: Option<impl VtxoRef>) -> Self {
91		if let Some(vtxo) = vtxo {
92			self.exited_vtxo(vtxo)
93		} else {
94			self
95		}
96	}
97
98	pub fn exited_vtxos(mut self, vtxos: impl IntoIterator<Item = impl VtxoRef>) -> Self {
99		let vtxos = vtxos.into_iter().map(|vtxo| vtxo.vtxo_id());
100		match &mut self.exited_vtxos {
101			None => self.exited_vtxos = Some(UpdateMethod::Merge(vtxos.collect())),
102			Some(vec) => vec.merge(vtxos),
103		}
104		self
105	}
106
107	pub fn fee(mut self, offchain_fee: Amount) -> Self {
108		self.offchain_fee = Some(offchain_fee);
109		self
110	}
111
112	pub fn intended_balance(mut self, intended: SignedAmount) -> Self {
113		self.intended_balance = Some(intended);
114		self
115	}
116
117	pub fn intended_and_effective_balance(mut self, balance: SignedAmount) -> Self {
118		self.intended_balance = Some(balance);
119		self.effective_balance = Some(balance);
120		self
121	}
122
123	pub fn metadata(
124		mut self,
125		metadata: impl IntoIterator<Item = (String, serde_json::Value)>,
126	) -> Self {
127		match &mut self.metadata {
128			None => self.metadata = Some(UpdateMethod::Merge(metadata.into_iter().collect())),
129			Some(map) => map.insert(metadata),
130		}
131		self
132	}
133
134	pub fn produced_vtxo(self, vtxo: impl VtxoRef) -> Self {
135		self.produced_vtxos([vtxo])
136	}
137
138	pub fn produced_vtxo_if_some(self, vtxo: Option<impl VtxoRef>) -> Self {
139		if let Some(vtxo) = vtxo {
140			self.produced_vtxo(vtxo)
141		} else {
142			self
143		}
144	}
145
146	pub fn produced_vtxos(mut self, vtxos: impl IntoIterator<Item = impl VtxoRef>) -> Self {
147		let vtxos = vtxos.into_iter().map(|v| v.vtxo_id());
148		match &mut self.produced_vtxos {
149			None => self.produced_vtxos = Some(UpdateMethod::Merge(vtxos.collect())),
150			Some(vec) => vec.merge(vtxos),
151		}
152		self
153	}
154
155	pub fn received_on(mut self, received: impl IntoIterator<Item = MovementDestination>) -> Self {
156		match &mut self.received_on {
157			None => self.received_on = Some(UpdateMethod::Merge(received.into_iter().collect())),
158			Some(vec) => vec.merge(received),
159		}
160		self
161	}
162
163	pub fn sent_to(mut self, destinations: impl IntoIterator<Item = MovementDestination>) -> Self {
164		match &mut self.sent_to {
165			None => self.sent_to = Some(UpdateMethod::Merge(destinations.into_iter().collect())),
166			Some(vec) => vec.merge(destinations),
167		}
168		self
169	}
170
171	pub fn replace_consumed_vtxos(mut self, vtxos: impl IntoIterator<Item = impl VtxoRef>) -> Self {
172		self.consumed_vtxos = Some(UpdateMethod::Replace(
173			vtxos.into_iter().map(|v| v.vtxo_id()).collect(),
174		));
175		self
176	}
177
178	pub fn replace_exited_vtxos(mut self, vtxos: impl IntoIterator<Item = impl VtxoRef>) -> Self {
179		self.exited_vtxos = Some(UpdateMethod::Replace(
180			vtxos.into_iter().map(|v| v.vtxo_id()).collect(),
181		));
182		self
183	}
184
185	pub fn replace_metadata(
186		mut self,
187		metadata: impl IntoIterator<Item = (String, serde_json::Value)>,
188	) -> Self {
189		self.metadata = Some(UpdateMethod::Replace(metadata.into_iter().collect()));
190		self
191	}
192
193	pub fn replace_produced_vtxos(mut self, vtxos: impl IntoIterator<Item = impl VtxoRef>) -> Self {
194		self.produced_vtxos = Some(UpdateMethod::Replace(
195			vtxos.into_iter().map(|v| v.vtxo_id()).collect(),
196		));
197		self
198	}
199
200	pub fn replace_received_on(
201		mut self,
202		received: impl IntoIterator<Item = MovementDestination>,
203	) -> Self {
204		self.received_on = Some(UpdateMethod::Replace(
205			received.into_iter().collect(),
206		));
207		self
208	}
209
210	pub fn replace_sent_on(
211		mut self,
212		destinations: impl IntoIterator<Item = MovementDestination>,
213	) -> Self {
214		self.sent_to = Some(UpdateMethod::Replace(
215			destinations.into_iter().collect(),
216		));
217		self
218	}
219
220	pub fn apply_to(self, movement: &mut Movement, at: DateTime<chrono::Local>) {
221		movement.time.updated_at = at;
222		if let Some(metadata) = self.metadata {
223			metadata.apply_to(&mut movement.metadata);
224		}
225		if let Some(intended) = self.intended_balance {
226			movement.intended_balance = intended;
227		}
228		if let Some(effective) = self.effective_balance {
229			movement.effective_balance = effective;
230		}
231		if let Some(offchain_fee) = self.offchain_fee {
232			movement.offchain_fee = offchain_fee;
233		}
234		if let Some(sent_to) = self.sent_to {
235			sent_to.apply_to(&mut movement.sent_to);
236		}
237		if let Some(received_on) = self.received_on {
238			received_on.apply_to(&mut movement.received_on);
239		}
240		if let Some(input_vtxos) = self.consumed_vtxos {
241			input_vtxos.apply_unique_to(&mut movement.input_vtxos);
242		}
243		if let Some(output_vtxos) = self.produced_vtxos {
244			output_vtxos.apply_unique_to(&mut movement.output_vtxos);
245		}
246		if let Some(exited_vtxos) = self.exited_vtxos {
247			exited_vtxos.apply_unique_to(&mut movement.exited_vtxos);
248		}
249	}
250}
251
252impl<T: PartialEq + Eq + Ord> UpdateMethod<Vec<T>> {
253	pub fn apply_to(self, target: &mut Vec<T>) {
254		match self {
255			UpdateMethod::Merge(vec) => target.extend(vec),
256			UpdateMethod::Replace(vec) => *target = vec,
257		}
258	}
259
260	pub fn apply_unique_to(self, target: &mut Vec<T>) {
261		match self {
262			UpdateMethod::Merge(vec) => target.merge(vec),
263			UpdateMethod::Replace(vec) => *target = vec,
264		}
265		target.sort_unstable();
266		target.dedup();
267	}
268
269	pub fn merge(&mut self, values: impl IntoIterator<Item = T>) {
270		let vec = match self {
271			UpdateMethod::Merge(vec) => vec,
272			UpdateMethod::Replace(vec) => vec,
273		};
274		values.into_iter().for_each(|value| vec.push(value));
275	}
276}
277
278impl<K: Eq + Hash, V> UpdateMethod<HashMap<K, V>> {
279	pub fn apply_to(self, target: &mut HashMap<K, V>) {
280		match self {
281			UpdateMethod::Merge(map) => target.extend(map),
282			UpdateMethod::Replace(map) => *target = map,
283		}
284	}
285
286	pub fn insert(&mut self, key_pairs: impl IntoIterator<Item = (K, V)>) {
287		let map = match self {
288			UpdateMethod::Merge(map) => map,
289			UpdateMethod::Replace(map) => map,
290		};
291		for (key, value) in key_pairs {
292			map.insert(key, value);
293		}
294	}
295}