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