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#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
12enum UpdateMethod<T> {
13 Merge(T),
15 Replace(T),
17}
18
19#[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}