Skip to main content

bark/movement/
update.rs

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