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