1use std::borrow::Cow;
12
13use bitcoin::{Amount, Transaction};
14use bitcoin::secp256k1::Keypair;
15use lightning_invoice::Bolt11Invoice;
16
17use ark::{Vtxo, VtxoId, VtxoPolicy, VtxoRequest};
18use ark::musig::DangerousSecretNonce;
19use ark::tree::signed::{UnlockHash, VtxoTreeSpec};
20use ark::lightning::{Invoice, PaymentHash, Preimage};
21use ark::rounds::RoundSeq;
22use bitcoin_ext::BlockDelta;
23
24use crate::WalletVtxo;
25use crate::exit::{ExitVtxo, ExitState};
26use crate::movement::MovementId;
27use crate::round::{AttemptState, RoundFlowState, RoundParticipation, RoundState};
28
29#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct PendingBoard {
32 pub funding_tx: Transaction,
35 pub vtxos: Vec<VtxoId>,
39 pub amount: Amount,
41 pub movement_id: MovementId,
43}
44
45#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct LightningSend {
50 pub invoice: Invoice,
52 pub amount: Amount,
54 pub htlc_vtxos: Vec<WalletVtxo>,
56 pub movement_id: MovementId,
58 pub preimage: Option<Preimage>,
65 pub finished_at: Option<chrono::DateTime<chrono::Local>>,
67}
68
69#[derive(Debug, Clone)]
76pub struct LightningReceive {
77 pub payment_hash: PaymentHash,
78 pub payment_preimage: Preimage,
79 pub invoice: Bolt11Invoice,
80 pub preimage_revealed_at: Option<chrono::DateTime<chrono::Local>>,
81 pub htlc_vtxos: Option<Vec<WalletVtxo>>,
82 pub htlc_recv_cltv_delta: BlockDelta,
83 pub movement_id: Option<MovementId>,
84 pub finished_at: Option<chrono::DateTime<chrono::Local>>,
85}
86
87pub struct StoredExit {
92 pub vtxo_id: VtxoId,
94 pub state: ExitState,
96 pub history: Vec<ExitState>,
98}
99
100impl StoredExit {
101 pub fn new(exit: &ExitVtxo) -> Self {
103 Self {
104 vtxo_id: exit.id(),
105 state: exit.state().clone(),
106 history: exit.history().clone(),
107 }
108 }
109}
110
111#[derive(Debug, Clone, Deserialize, Serialize)]
112struct SerdeVtxoRequest<'a> {
113 #[serde(with = "bitcoin::amount::serde::as_sat")]
114 amount: Amount,
115 #[serde(with = "ark::encode::serde")]
116 policy: Cow<'a, VtxoPolicy>,
117}
118
119impl<'a> From<&'a VtxoRequest> for SerdeVtxoRequest<'a> {
120 fn from(v: &'a VtxoRequest) -> Self {
121 Self {
122 amount: v.amount,
123 policy: Cow::Borrowed(&v.policy),
124 }
125 }
126}
127
128impl<'a> From<SerdeVtxoRequest<'a>> for VtxoRequest {
129 fn from(v: SerdeVtxoRequest<'a>) -> Self {
130 VtxoRequest {
131 amount: v.amount,
132 policy: v.policy.into_owned(),
133 }
134 }
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139struct SerdeRoundParticipation<'a> {
140 #[serde(with = "ark::encode::serde::cow::vec")]
141 inputs: Cow<'a, [Vtxo]>,
142 outputs: Vec<SerdeVtxoRequest<'a>>,
143}
144
145impl<'a> From<&'a RoundParticipation> for SerdeRoundParticipation<'a> {
146 fn from(v: &'a RoundParticipation) -> Self {
147 Self {
148 inputs: Cow::Borrowed(&v.inputs),
149 outputs: v.outputs.iter().map(|v| v.into()).collect(),
150 }
151 }
152}
153
154impl<'a> From<SerdeRoundParticipation<'a>> for RoundParticipation {
155 fn from(v: SerdeRoundParticipation<'a>) -> Self {
156 Self {
157 inputs: v.inputs.into_owned(),
158 outputs: v.outputs.into_iter().map(|v| v.into()).collect(),
159 }
160 }
161}
162
163#[derive(Debug, Serialize, Deserialize)]
165enum SerdeAttemptState<'a> {
166 AwaitingAttempt,
167 AwaitingUnsignedVtxoTree {
168 cosign_keys: Cow<'a, [Keypair]>,
169 secret_nonces: Cow<'a, [Vec<DangerousSecretNonce>]>,
170 unlock_hash: UnlockHash,
171 },
172 AwaitingFinishedRound {
173 #[serde(with = "bitcoin_ext::serde::encodable::cow")]
174 unsigned_round_tx: Cow<'a, Transaction>,
175 #[serde(with = "ark::encode::serde")]
176 vtxos_spec: Cow<'a, VtxoTreeSpec>,
177 unlock_hash: UnlockHash,
178 },
179}
180
181impl<'a> From<&'a AttemptState> for SerdeAttemptState<'a> {
182 fn from(state: &'a AttemptState) -> Self {
183 match state {
184 AttemptState::AwaitingAttempt => SerdeAttemptState::AwaitingAttempt,
185 AttemptState::AwaitingUnsignedVtxoTree { cosign_keys, secret_nonces, unlock_hash } => {
186 SerdeAttemptState::AwaitingUnsignedVtxoTree {
187 cosign_keys: Cow::Borrowed(cosign_keys),
188 secret_nonces: Cow::Borrowed(secret_nonces),
189 unlock_hash: *unlock_hash,
190 }
191 },
192 AttemptState::AwaitingFinishedRound { unsigned_round_tx, vtxos_spec, unlock_hash } => {
193 SerdeAttemptState::AwaitingFinishedRound {
194 unsigned_round_tx: Cow::Borrowed(unsigned_round_tx),
195 vtxos_spec: Cow::Borrowed(vtxos_spec),
196 unlock_hash: *unlock_hash,
197 }
198 },
199 }
200 }
201}
202
203impl<'a> From<SerdeAttemptState<'a>> for AttemptState {
204 fn from(state: SerdeAttemptState<'a>) -> Self {
205 match state {
206 SerdeAttemptState::AwaitingAttempt => AttemptState::AwaitingAttempt,
207 SerdeAttemptState::AwaitingUnsignedVtxoTree { cosign_keys, secret_nonces, unlock_hash } => {
208 AttemptState::AwaitingUnsignedVtxoTree {
209 cosign_keys: cosign_keys.into_owned(),
210 secret_nonces: secret_nonces.into_owned(),
211 unlock_hash: unlock_hash,
212 }
213 },
214 SerdeAttemptState::AwaitingFinishedRound { unsigned_round_tx, vtxos_spec, unlock_hash } => {
215 AttemptState::AwaitingFinishedRound {
216 unsigned_round_tx: unsigned_round_tx.into_owned(),
217 vtxos_spec: vtxos_spec.into_owned(),
218 unlock_hash: unlock_hash,
219 }
220 },
221 }
222 }
223}
224
225#[derive(Debug, Serialize, Deserialize)]
227enum SerdeRoundFlowState<'a> {
228 NonInteractivePending {
230 unlock_hash: UnlockHash,
231 },
232
233 InteractivePending,
235 InteractiveOngoing {
237 round_seq: RoundSeq,
238 attempt_seq: usize,
239 state: SerdeAttemptState<'a>,
240 },
241
242 Finished {
244 funding_tx: Cow<'a, Transaction>,
245 unlock_hash: UnlockHash,
246 },
247
248 Failed {
250 error: Cow<'a, str>,
251 },
252
253 Canceled,
255}
256
257impl<'a> From<&'a RoundFlowState> for SerdeRoundFlowState<'a> {
258 fn from(state: &'a RoundFlowState) -> Self {
259 match state {
260 RoundFlowState::NonInteractivePending { unlock_hash } => {
261 SerdeRoundFlowState::NonInteractivePending {
262 unlock_hash: *unlock_hash,
263 }
264 },
265 RoundFlowState::InteractivePending => SerdeRoundFlowState::InteractivePending,
266 RoundFlowState::InteractiveOngoing { round_seq, attempt_seq, state } => {
267 SerdeRoundFlowState::InteractiveOngoing {
268 round_seq: *round_seq,
269 attempt_seq: *attempt_seq,
270 state: state.into(),
271 }
272 },
273 RoundFlowState::Finished { funding_tx, unlock_hash } => {
274 SerdeRoundFlowState::Finished {
275 funding_tx: Cow::Borrowed(funding_tx),
276 unlock_hash: *unlock_hash,
277 }
278 },
279 RoundFlowState::Failed { error } => {
280 SerdeRoundFlowState::Failed {
281 error: Cow::Borrowed(error),
282 }
283 },
284 RoundFlowState::Canceled => SerdeRoundFlowState::Canceled,
285 }
286 }
287}
288
289impl<'a> From<SerdeRoundFlowState<'a>> for RoundFlowState {
290 fn from(state: SerdeRoundFlowState<'a>) -> Self {
291 match state {
292 SerdeRoundFlowState::NonInteractivePending { unlock_hash } => {
293 RoundFlowState::NonInteractivePending { unlock_hash }
294 },
295 SerdeRoundFlowState::InteractivePending => RoundFlowState::InteractivePending,
296 SerdeRoundFlowState::InteractiveOngoing { round_seq, attempt_seq, state } => {
297 RoundFlowState::InteractiveOngoing {
298 round_seq: round_seq,
299 attempt_seq: attempt_seq,
300 state: state.into(),
301 }
302 },
303 SerdeRoundFlowState::Finished { funding_tx, unlock_hash } => {
304 RoundFlowState::Finished {
305 funding_tx: funding_tx.into_owned(),
306 unlock_hash,
307 }
308 },
309 SerdeRoundFlowState::Failed { error } => {
310 RoundFlowState::Failed {
311 error: error.into_owned(),
312 }
313 },
314 SerdeRoundFlowState::Canceled => RoundFlowState::Canceled,
315 }
316 }
317}
318
319#[derive(Debug, Serialize, Deserialize)]
321pub struct SerdeRoundState<'a> {
322 done: bool,
323 participation: SerdeRoundParticipation<'a>,
324 movement_id: Option<MovementId>,
325 flow: SerdeRoundFlowState<'a>,
326 #[serde(with = "ark::encode::serde::cow::vec")]
327 new_vtxos: Cow<'a, [Vtxo]>,
328 sent_forfeit_sigs: bool,
329}
330
331impl<'a> From<&'a RoundState> for SerdeRoundState<'a> {
332 fn from(state: &'a RoundState) -> Self {
333 Self {
334 done: state.done,
335 participation: (&state.participation).into(),
336 movement_id: state.movement_id,
337 flow: (&state.flow).into(),
338 new_vtxos: Cow::Borrowed(&state.new_vtxos),
339 sent_forfeit_sigs: state.sent_forfeit_sigs,
340 }
341 }
342}
343
344impl<'a> From<SerdeRoundState<'a>> for RoundState {
345 fn from(state: SerdeRoundState<'a>) -> Self {
346 Self {
347 done: state.done,
348 participation: state.participation.into(),
349 movement_id: state.movement_id,
350 flow: state.flow.into(),
351 new_vtxos: state.new_vtxos.into_owned(),
352 sent_forfeit_sigs: state.sent_forfeit_sigs,
353 }
354 }
355}
356
357#[cfg(test)]
358mod test {
359 use crate::exit::{ExitState, ExitTxOrigin};
360 use crate::vtxo::VtxoState;
361
362 #[test]
363 fn test_serialised_structs() {
366 let serialised = r#"{"type":"start","tip_height":119}"#;
368 serde_json::from_str::<ExitState>(serialised).unwrap();
369 let serialised = r#"{"type":"processing","tip_height":119,"transactions":[{"txid":"9fd34b8c556dd9954bda80ba2cf3474a372702ebc31a366639483e78417c6812","status":{"type":"awaiting-input-confirmation","txids":["ddfe11920358d1a1fae970dc80459c60675bf1392896f69b103fc638313751de"]}}]}"#;
370 serde_json::from_str::<ExitState>(serialised).unwrap();
371 let serialised = r#"{"type":"awaiting-delta","tip_height":122,"confirmed_block":"122:3cdd30fc942301a74666c481beb82050ccd182050aee3c92d2197e8cad427b8f","claimable_height":134}"#;
372 serde_json::from_str::<ExitState>(serialised).unwrap();
373 let serialised = r#"{"type":"claimable","tip_height":134,"claimable_since": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9","last_scanned_block":null}"#;
374 serde_json::from_str::<ExitState>(serialised).unwrap();
375 let serialised = r#"{"type":"claim-in-progress","tip_height":134, "claimable_since": "134:6585896bdda6f08d924bf45cc2b16418af56703b3c50930e4dccbc1728d3800a","claim_txid":"599347c35870bd36f7acb22b81f9ffa8b911d9b5e94834858aebd3ec09339f4c"}"#;
376 serde_json::from_str::<ExitState>(serialised).unwrap();
377 let serialised = r#"{"type":"claimed","tip_height":134,"txid":"599347c35870bd36f7acb22b81f9ffa8b911d9b5e94834858aebd3ec09339f4c","block": "122:3cdd30fc942301a74666c481beb82050ccd182050aee3c92d2197e8cad427b8f"}"#;
378 serde_json::from_str::<ExitState>(serialised).unwrap();
379
380 let serialized = r#"{"type":"wallet","confirmed_in":null}"#;
382 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
383 let serialized = r#"{"type":"wallet","confirmed_in": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9"}"#;
384 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
385 let serialized = r#"{"type":"mempool","fee_rate_kwu":25000,"total_fee":27625}"#;
386 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
387 let serialized = r#"{"type":"block","confirmed_in": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9"}"#;
388 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
389
390 let serialised = r#"{"type": "spendable"}"#;
392 serde_json::from_str::<VtxoState>(serialised).unwrap();
393 let serialised = r#"{"type": "spent"}"#;
394 serde_json::from_str::<VtxoState>(serialised).unwrap();
395 let serialised = r#"{"type": "locked", "movement_id": null}"#;
396 serde_json::from_str::<VtxoState>(serialised).unwrap();
397 }
398}