1pub mod fees;
2#[cfg(feature = "onchain-bdk")]
3pub mod onchain;
4
5use std::borrow::Borrow;
6use std::time::Duration;
7
8use bitcoin::secp256k1::{schnorr, PublicKey};
9use bitcoin::{Amount, Txid};
10#[cfg(feature = "utoipa")]
11use utoipa::ToSchema;
12
13use ark::VtxoId;
14use ark::lightning::{PaymentHash, Preimage};
15use bitcoin_ext::{AmountExt, BlockDelta};
16
17use bark::actions::lightning::pay::{LightningSendState, Progress as SendProgress};
18use bark::movement::MovementId;
19use bark::actions::lightning::receive::{
20 LightningReceive, LightningReceiveState, Progress as ReceiveProgress,
21};
22
23use crate::cli::fees::FeeSchedule;
24use crate::exit::error::ExitError;
25use crate::exit::package::ExitTransactionPackage;
26use crate::exit::ExitState;
27use crate::primitives::{TransactionInfo, WalletVtxoInfo};
28use crate::serde_utils;
29
30#[derive(Debug, Clone, Serialize)]
31#[cfg_attr(feature = "utoipa", derive(ToSchema))]
32pub struct ArkInfo {
33 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
35 pub network: bitcoin::Network,
36 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
38 pub server_pubkey: PublicKey,
39 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
41 pub mailbox_pubkey: PublicKey,
42 #[serde(with = "serde_utils::duration")]
44 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
45 pub round_interval: Duration,
46 pub nb_round_nonces: usize,
48 #[cfg_attr(feature = "utoipa", schema(value_type = u16))]
50 pub vtxo_exit_delta: BlockDelta,
51 #[serde(default)]
53 #[cfg_attr(feature = "utoipa", schema(value_type = u16))]
54 pub vtxo_lifetime: BlockDelta,
55 #[cfg_attr(feature = "utoipa", schema(value_type = u16))]
57 pub htlc_send_expiry_delta: BlockDelta,
58 #[cfg_attr(feature = "utoipa", schema(value_type = u16))]
60 pub htlc_expiry_delta: BlockDelta,
61 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
63 pub max_vtxo_amount: Option<Amount>,
64 pub required_board_confirmations: usize,
66 #[cfg_attr(feature = "utoipa", schema(value_type = u16))]
69 pub max_user_invoice_cltv_delta: BlockDelta,
70 #[serde(rename = "min_board_amount_sat", with = "bitcoin::amount::serde::as_sat")]
72 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
73 pub min_board_amount: Amount,
74 pub offboard_feerate_sat_per_kvb: u64,
76 pub ln_receive_anti_dos_required: bool,
80 pub fees: FeeSchedule,
82 pub max_vtxo_exit_depth: u16,
87 pub tos_link: Option<String>,
89 pub max_offboard_inputs: usize,
91
92 #[deprecated(note = "renamed to `vtxo_lifetime`")]
98 #[serde(default)]
99 #[cfg_attr(feature = "utoipa", schema(required = true, value_type = u16))]
100 pub vtxo_expiry_delta: BlockDelta,
101}
102
103impl<'de> serde::Deserialize<'de> for ArkInfo {
104 #[allow(deprecated)]
105 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
106 #[derive(Deserialize)]
107 struct ArkInfoStub {
108 network: bitcoin::Network,
109 server_pubkey: PublicKey,
110 mailbox_pubkey: PublicKey,
111 #[serde(with = "serde_utils::duration")]
112 round_interval: Duration,
113 nb_round_nonces: usize,
114 vtxo_exit_delta: BlockDelta,
115 #[serde(default)]
116 vtxo_lifetime: BlockDelta,
117 htlc_send_expiry_delta: BlockDelta,
118 htlc_expiry_delta: BlockDelta,
119 max_vtxo_amount: Option<Amount>,
120 required_board_confirmations: usize,
121 max_user_invoice_cltv_delta: BlockDelta,
122 #[serde(rename = "min_board_amount_sat", with = "bitcoin::amount::serde::as_sat")]
123 min_board_amount: Amount,
124 offboard_feerate_sat_per_kvb: u64,
125 ln_receive_anti_dos_required: bool,
126 fees: FeeSchedule,
127 max_vtxo_exit_depth: u16,
128 tos_link: Option<String>,
129 max_offboard_inputs: usize,
130 #[serde(default)]
131 vtxo_expiry_delta: BlockDelta,
132 }
133
134 let v = ArkInfoStub::deserialize(d)?;
135
136 let vtxo_lifetime = match (v.vtxo_lifetime, v.vtxo_expiry_delta) {
137 (BlockDelta::ZERO, expiry) => expiry,
138 (lifetime, BlockDelta::ZERO) => lifetime,
139 (lifetime, expiry) if lifetime == expiry => lifetime,
140 (lifetime, expiry) => return Err(serde::de::Error::custom(format!(
141 "vtxo_lifetime ({}) and vtxo_expiry_delta ({}) don't match", lifetime, expiry,
142 ))),
143 };
144
145 Ok(ArkInfo {
146 network: v.network,
147 server_pubkey: v.server_pubkey,
148 mailbox_pubkey: v.mailbox_pubkey,
149 round_interval: v.round_interval,
150 nb_round_nonces: v.nb_round_nonces,
151 vtxo_exit_delta: v.vtxo_exit_delta,
152 vtxo_lifetime: vtxo_lifetime,
153 vtxo_expiry_delta: vtxo_lifetime,
154 htlc_send_expiry_delta: v.htlc_send_expiry_delta,
155 htlc_expiry_delta: v.htlc_expiry_delta,
156 max_vtxo_amount: v.max_vtxo_amount,
157 required_board_confirmations: v.required_board_confirmations,
158 max_user_invoice_cltv_delta: v.max_user_invoice_cltv_delta,
159 min_board_amount: v.min_board_amount,
160 offboard_feerate_sat_per_kvb: v.offboard_feerate_sat_per_kvb,
161 ln_receive_anti_dos_required: v.ln_receive_anti_dos_required,
162 fees: v.fees,
163 max_vtxo_exit_depth: v.max_vtxo_exit_depth,
164 tos_link: v.tos_link,
165 max_offboard_inputs: v.max_offboard_inputs,
166 })
167 }
168}
169
170#[derive(Debug, Clone, Deserialize, Serialize)]
171#[cfg_attr(feature = "utoipa", derive(ToSchema))]
172pub struct NextRoundStart {
173 pub start_time: chrono::DateTime<chrono::Local>,
175}
176
177impl<T: Borrow<ark::ArkInfo>> From<T> for ArkInfo {
178 #[allow(deprecated)] fn from(v: T) -> Self {
180 let v = v.borrow();
181 ArkInfo {
182 network: v.network,
183 server_pubkey: v.server_pubkey,
184 mailbox_pubkey: v.mailbox_pubkey,
185 round_interval: v.round_interval,
186 nb_round_nonces: v.nb_round_nonces,
187 vtxo_exit_delta: v.vtxo_exit_delta,
188 vtxo_lifetime: v.vtxo_lifetime,
189 vtxo_expiry_delta: v.vtxo_lifetime,
192 htlc_send_expiry_delta: v.htlc_send_expiry_delta,
193 htlc_expiry_delta: v.htlc_expiry_delta,
194 max_vtxo_amount: v.max_vtxo_amount,
195 required_board_confirmations: v.required_board_confirmations,
196 max_user_invoice_cltv_delta: v.max_user_invoice_cltv_delta,
197 min_board_amount: v.min_board_amount,
198 offboard_feerate_sat_per_kvb: v.offboard_feerate.to_sat_per_kwu() * 4,
199 ln_receive_anti_dos_required: v.ln_receive_anti_dos_required,
200 fees: v.fees.clone().into(),
201 max_vtxo_exit_depth: v.max_vtxo_exit_depth,
202 max_offboard_inputs: v.max_offboard_inputs,
203 tos_link: v.tos_link.clone(),
204 }
205 }
206}
207
208#[derive(Debug, Clone, Deserialize, Serialize)]
210#[cfg_attr(feature = "utoipa", derive(ToSchema))]
211pub struct SignedMessage {
212 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
215 pub signature: schnorr::Signature,
216}
217
218#[derive(Debug, Clone, Deserialize, Serialize)]
220#[cfg_attr(feature = "utoipa", derive(ToSchema))]
221pub struct MessageVerification {
222 pub valid: bool,
224}
225
226#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
228#[cfg_attr(feature = "utoipa", derive(ToSchema))]
229pub struct DebugInfo {
230 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
232 pub network: bitcoin::Network,
233 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
235 pub mailbox_id: PublicKey,
236 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
238 pub vtxo_xpub: bitcoin::bip32::Xpub,
239}
240
241#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
251#[cfg_attr(feature = "utoipa", derive(ToSchema))]
252pub struct Balance {
253 #[serde(rename = "spendable_sat", with = "bitcoin::amount::serde::as_sat")]
256 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
257 pub spendable: Amount,
258 #[serde(default, rename = "needs_refresh_sat", with = "bitcoin::amount::serde::as_sat")]
263 #[cfg_attr(feature = "utoipa", schema(value_type = u64, required))]
264 pub needs_refresh: Amount,
265 #[serde(rename = "pending_lightning_send_sat", with = "bitcoin::amount::serde::as_sat")]
268 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
269 pub pending_lightning_send: Amount,
270 #[serde(rename = "claimable_lightning_receive_sat", with = "bitcoin::amount::serde::as_sat")]
274 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
275 pub claimable_lightning_receive: Amount,
276 #[serde(rename = "pending_in_round_sat", with = "bitcoin::amount::serde::as_sat")]
279 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
280 pub pending_in_round: Amount,
281 #[serde(rename = "pending_board_sat", with = "bitcoin::amount::serde::as_sat")]
284 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
285 pub pending_board: Amount,
286 #[serde(default, rename = "pending_arkoor_send_sat", with = "bitcoin::amount::serde::as_sat")]
290 #[cfg_attr(feature = "utoipa", schema(value_type = u64, required))]
291 pub pending_arkoor_send: Amount,
292 #[serde(default, rename = "pending_offboard_sat", with = "bitcoin::amount::serde::as_sat")]
296 #[cfg_attr(feature = "utoipa", schema(value_type = u64, required))]
297 pub pending_offboard: Amount,
298 #[serde(default, rename = "pending_exit_sat", with = "bitcoin::amount::serde::as_sat")]
303 #[cfg_attr(feature = "utoipa", schema(value_type = u64, required))]
304 pub pending_exit: Amount,
305}
306
307impl From<bark::DebugInfo> for DebugInfo {
308 fn from(v: bark::DebugInfo) -> Self {
309 DebugInfo {
310 network: v.network,
311 mailbox_id: v.mailbox_id.as_pubkey(),
312 vtxo_xpub: v.vtxo_xpub,
313 }
314 }
315}
316
317impl From<bark::Balance> for Balance {
318 fn from(v: bark::Balance) -> Self {
319 Balance {
320 spendable: v.spendable,
321 needs_refresh: v.needs_refresh,
322 pending_in_round: v.pending_in_round,
323 pending_lightning_send: v.pending_lightning_send,
324 claimable_lightning_receive: v.claimable_lightning_receive,
325 pending_exit: v.pending_exit,
326 pending_board: v.pending_board,
327 pending_arkoor_send: v.pending_arkoor_send,
328 pending_offboard: v.pending_offboard,
329 }
330 }
331}
332
333#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
339#[cfg_attr(feature = "utoipa", derive(ToSchema))]
340pub struct BalanceSummary {
341 #[serde(rename = "spendable_sat", with = "bitcoin::amount::serde::as_sat")]
343 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
344 pub spendable: Amount,
345 #[serde(rename = "needs_refresh_sat", with = "bitcoin::amount::serde::as_sat")]
348 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
349 pub needs_refresh: Amount,
350 #[serde(rename = "pending_sat", with = "bitcoin::amount::serde::as_sat")]
354 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
355 pub pending: Amount,
356 #[serde(rename = "total_sat", with = "bitcoin::amount::serde::as_sat")]
358 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
359 pub total: Amount,
360}
361
362impl From<&Balance> for BalanceSummary {
363 fn from(v: &Balance) -> Self {
364 let pending = v.pending_in_round
365 + v.pending_board
366 + v.pending_arkoor_send
367 + v.pending_lightning_send
368 + v.claimable_lightning_receive
369 + v.pending_offboard
370 + v.pending_exit;
371 BalanceSummary {
372 spendable: v.spendable,
373 needs_refresh: v.needs_refresh,
374 pending,
375 total: v.spendable + v.needs_refresh + pending,
376 }
377 }
378}
379
380impl From<Balance> for BalanceSummary {
381 fn from(v: Balance) -> Self {
382 BalanceSummary::from(&v)
383 }
384}
385
386impl From<bark::BalanceSummary> for BalanceSummary {
387 fn from(v: bark::BalanceSummary) -> Self {
388 BalanceSummary {
389 spendable: v.spendable,
390 needs_refresh: v.needs_refresh,
391 pending: v.pending,
392 total: v.total,
393 }
394 }
395}
396
397#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
398#[cfg_attr(feature = "utoipa", derive(ToSchema))]
399pub struct ExitProgressResponse {
400 pub exits: Vec<ExitProgressStatus>,
402 pub done: bool,
404 pub claimable_height: Option<u32>,
406 #[serde(default, skip_serializing_if = "Option::is_none")]
411 pub error: Option<ExitError>,
412}
413
414#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
415#[cfg_attr(feature = "utoipa", derive(ToSchema))]
416pub struct ExitProgressStatus {
417 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
419 pub vtxo_id: VtxoId,
420 pub state: ExitState,
422 #[serde(default, skip_serializing_if = "Option::is_none")]
424 pub error: Option<ExitError>,
425}
426
427impl From<bark::exit::ExitProgressStatus> for ExitProgressStatus {
428 fn from(v: bark::exit::ExitProgressStatus) -> Self {
429 ExitProgressStatus {
430 vtxo_id: v.vtxo_id,
431 state: v.state.into(),
432 error: v.error.map(ExitError::from),
433 }
434 }
435}
436
437#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
438#[cfg_attr(feature = "utoipa", derive(ToSchema))]
439pub struct ExitTransactionStatus {
440 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
442 pub vtxo_id: VtxoId,
443 pub state: ExitState,
445 #[serde(default, skip_serializing_if = "Option::is_none")]
447 pub history: Option<Vec<ExitState>>,
448 #[serde(default, skip_serializing_if = "Vec::is_empty")]
450 pub transactions: Vec<ExitTransactionPackage>,
451}
452
453impl From<bark::exit::ExitTransactionStatus> for ExitTransactionStatus {
454 fn from(v: bark::exit::ExitTransactionStatus) -> Self {
455 ExitTransactionStatus {
456 vtxo_id: v.vtxo_id,
457 state: v.state.into(),
458 history: v.history.map(|h| h.into_iter().map(ExitState::from).collect()),
459 transactions: v.transactions.into_iter().map(ExitTransactionPackage::from).collect(),
460 }
461 }
462}
463
464#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
466#[cfg_attr(feature = "utoipa", derive(ToSchema))]
467pub struct PendingBoardInfo {
468 pub funding_tx: TransactionInfo,
472 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
477 pub vtxos: Vec<VtxoId>,
478 #[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
480 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
481 pub amount: Amount,
482 pub movement_id: u32,
484}
485
486impl From<bark::persist::models::PendingBoard> for PendingBoardInfo {
487 fn from(v: bark::persist::models::PendingBoard) -> Self {
488 PendingBoardInfo {
489 funding_tx: v.funding_tx.into(),
490 vtxos: v.vtxos,
491 amount: v.amount,
492 movement_id: v.movement_id.0,
493 }
494 }
495}
496
497#[derive(Debug, Clone, Serialize, Deserialize)]
498#[serde(tag = "status", rename_all = "kebab-case")]
499#[cfg_attr(feature = "utoipa", derive(ToSchema))]
500pub enum RoundStatus {
501 SyncError {
503 error: String,
504 },
505 Confirmed {
507 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
508 funding_txid: Txid,
509 },
510 Unconfirmed {
512 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
513 funding_txid: Txid,
514 },
515 Pending,
517 Failed {
519 error: String,
520 },
521 Canceled,
523}
524
525impl RoundStatus {
526 pub fn is_final(&self) -> bool {
528 match self {
529 Self::SyncError { .. } => false,
530 Self::Confirmed { .. } => true,
531 Self::Unconfirmed { .. } => false,
532 Self::Pending { .. } => false,
533 Self::Failed { .. } => true,
534 Self::Canceled => true,
535 }
536 }
537
538 pub fn is_success(&self) -> bool {
540 match self {
541 Self::SyncError { .. } => false,
542 Self::Confirmed { .. } => true,
543 Self::Unconfirmed { .. } => true,
544 Self::Pending { .. } => false,
545 Self::Failed { .. } => false,
546 Self::Canceled => false,
547 }
548 }
549}
550
551impl From<bark::round::RoundStatus> for RoundStatus {
552 fn from(s: bark::round::RoundStatus) -> Self {
553 match s {
554 bark::round::RoundStatus::Confirmed { funding_txid } => {
555 Self::Confirmed { funding_txid }
556 },
557 bark::round::RoundStatus::Unconfirmed { funding_txid } => {
558 Self::Unconfirmed { funding_txid }
559 },
560 bark::round::RoundStatus::Pending => Self::Pending,
561 bark::round::RoundStatus::Failed { error } => Self::Failed { error },
562 bark::round::RoundStatus::Canceled => Self::Canceled,
563 }
564 }
565}
566
567#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
568#[cfg_attr(feature = "utoipa", derive(ToSchema))]
569pub struct RoundStateInfo {
570 pub round_state_id: u32,
571}
572
573#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
574#[cfg_attr(feature = "utoipa", derive(ToSchema))]
575pub struct InvoiceInfo {
576 pub invoice: String,
578}
579
580#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
581#[cfg_attr(feature = "utoipa", derive(ToSchema))]
582pub struct OffboardResult {
583 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
585 pub offboard_txid: Txid,
586 #[cfg_attr(feature = "utoipa", schema(value_type = u32))]
588 pub movement_id: MovementId,
589}
590
591#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
592#[cfg_attr(feature = "utoipa", derive(ToSchema))]
593pub struct LightningReceiveInfo {
594 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
596 pub payment_hash: PaymentHash,
597 pub state: String,
600 pub invoice: String,
602 #[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
604 pub payment_preimage: Option<Preimage>,
605 #[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
607 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
608 pub amount: Amount,
609 #[serde(default, deserialize_with = "serde_utils::null_as_default")]
613 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>, required = true))]
614 pub htlc_vtxo_ids: Vec<VtxoId>,
615 pub settled_at: Option<chrono::DateTime<chrono::Local>>,
617
618 #[deprecated(note = "no longer tracked; use `state` and `settled_at`")]
620 #[serde(default)]
621 pub preimage_revealed_at: Option<chrono::DateTime<chrono::Local>>,
622 #[deprecated(note = "renamed to `settled_at`")]
624 #[serde(default)]
625 pub finished_at: Option<chrono::DateTime<chrono::Local>>,
626 #[deprecated(note = "replaced by `htlc_vtxo_ids`")]
628 #[serde(default, deserialize_with = "serde_utils::null_as_default")]
629 #[cfg_attr(feature = "utoipa", schema(required = true))]
630 pub htlc_vtxos: Vec<WalletVtxoInfo>,
631}
632
633impl LightningReceiveInfo {
634 #[allow(deprecated)] pub fn from_state(state: &LightningReceiveState) -> Self {
637 match state {
638 LightningReceiveState::InProgress(recv) => LightningReceiveInfo::from(recv),
639 LightningReceiveState::Settled(s) => LightningReceiveInfo {
640 payment_hash: s.payment_hash,
641 state: "settled".to_string(),
642 invoice: s.invoice.to_string(),
643 payment_preimage: Some(s.preimage),
644 amount: s.amount,
645 htlc_vtxo_ids: vec![],
646 settled_at: Some(s.settled_at),
647 preimage_revealed_at: None,
648 finished_at: Some(s.settled_at),
649 htlc_vtxos: vec![],
650 },
651 }
652 }
653}
654
655#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
656#[cfg_attr(feature = "utoipa", derive(ToSchema))]
657pub struct LightningSendInfo {
658 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
660 pub payment_hash: PaymentHash,
661 pub state: String,
664 pub invoice: Option<String>,
666 #[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
668 pub preimage: Option<Preimage>,
669}
670
671impl LightningSendInfo {
672 pub fn from_state(hash: PaymentHash, state: &LightningSendState) -> Self {
674 match state {
675 LightningSendState::Unknown => LightningSendInfo {
676 payment_hash: hash,
677 state: "unknown".to_string(),
678 invoice: None,
679 preimage: None,
680 },
681 LightningSendState::Paid(paid) => LightningSendInfo {
682 payment_hash: paid.payment_hash,
683 state: "paid".to_string(),
684 invoice: None,
685 preimage: Some(paid.preimage),
686 },
687 LightningSendState::InProgress(send) => {
688 let phase = match send.progress {
689 SendProgress::Start => "start",
690 SendProgress::InputsLocked { .. } => "inputs-locked",
691 SendProgress::HtlcReceived(_) => "htlc-received",
692 SendProgress::PaymentInitiated(_) => "payment-initiated",
693 SendProgress::RevocableHtlcs { .. } => "revocable-htlcs",
694 SendProgress::RevocationStuck { .. } => "revocation-stuck",
695 };
696 LightningSendInfo {
697 payment_hash: send.invoice.payment_hash(),
698 state: phase.to_string(),
699 invoice: Some(send.invoice.to_string()),
700 preimage: None,
701 }
702 },
703 }
704 }
705}
706
707impl From<&LightningReceive> for LightningReceiveInfo {
708 #[allow(deprecated)] fn from(recv: &LightningReceive) -> Self {
710 let (state, htlc_vtxo_ids) = match &recv.progress {
711 ReceiveProgress::AwaitingPayment => ("awaiting-payment", vec![]),
712 ReceiveProgress::HtlcsReady(htlcs) => ("htlcs-ready", htlcs.vtxo_ids.clone()),
713 ReceiveProgress::PreimageRevealed(htlcs) => ("preimage-revealed", htlcs.vtxo_ids.clone()),
714 ReceiveProgress::Delivering(_) => ("delivering", vec![]),
716 };
717 LightningReceiveInfo {
718 payment_hash: recv.payment_hash,
719 state: state.to_string(),
720 invoice: recv.invoice.to_string(),
721 payment_preimage: Some(recv.payment_preimage),
722 amount: recv.invoice.amount_milli_satoshis()
723 .map(Amount::from_msat_floor)
724 .expect("generated invoice with no amount"),
725 htlc_vtxo_ids,
726 settled_at: None,
727 preimage_revealed_at: None,
728 finished_at: None,
729 htlc_vtxos: vec![],
730 }
731 }
732}
733
734#[cfg(test)]
735mod test {
736 use bitcoin::FeeRate;
737 use super::*;
738
739 fn lightning_receive_base_json() -> serde_json::Value {
740 serde_json::json!({
741 "amount_sat": 1000,
742 "payment_hash": "0000000000000000000000000000000000000000000000000000000000000000",
743 "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000",
744 "state": "awaiting-payment",
745 "settled_at": null,
746 "invoice": "lnbc1",
747 })
748 }
749
750 #[test]
751 fn deserialize_lightning_receive_htlc_vtxo_ids_missing() {
752 let json = lightning_receive_base_json();
753 serde_json::from_value::<LightningReceiveInfo>(json).unwrap();
754 }
755
756 #[test]
757 fn deserialize_lightning_receive_htlc_vtxo_ids_null() {
758 let mut json = lightning_receive_base_json();
759 json["htlc_vtxo_ids"] = serde_json::json!(null);
760 serde_json::from_value::<LightningReceiveInfo>(json).unwrap();
761 }
762
763 #[test]
764 fn deserialize_lightning_receive_htlc_vtxo_ids_empty() {
765 let mut json = lightning_receive_base_json();
766 json["htlc_vtxo_ids"] = serde_json::json!([]);
767 serde_json::from_value::<LightningReceiveInfo>(json).unwrap();
768 }
769
770 #[allow(deprecated)]
771 fn ark_info_base() -> ArkInfo {
772 let pubkey = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
773 .parse::<PublicKey>().unwrap();
774 ArkInfo {
775 network: bitcoin::Network::Regtest,
776 server_pubkey: pubkey,
777 mailbox_pubkey: pubkey,
778 round_interval: Duration::from_secs(60),
779 nb_round_nonces: 1,
780 vtxo_exit_delta: BlockDelta::new(12),
781 vtxo_lifetime: BlockDelta::new(100),
782 vtxo_expiry_delta: BlockDelta::new(100),
783 htlc_send_expiry_delta: BlockDelta::new(100),
784 htlc_expiry_delta: BlockDelta::new(6),
785 max_vtxo_amount: None,
786 required_board_confirmations: 3,
787 max_user_invoice_cltv_delta: BlockDelta::new(100),
788 min_board_amount: Amount::from_sat(1000),
789 offboard_feerate_sat_per_kvb: 1000,
790 ln_receive_anti_dos_required: false,
791 fees: ark::fees::FeeSchedule::default().into(),
792 max_vtxo_exit_depth: 10,
793 tos_link: None,
794 max_offboard_inputs: 4,
795 }
796 }
797
798 #[test]
799 #[allow(deprecated)]
800 fn ark_info_vtxo_lifetime_falls_back_to_deprecated_field() {
801 let mut json = serde_json::to_value(ark_info_base()).unwrap();
803 json.as_object_mut().unwrap().remove("vtxo_lifetime");
804 json["vtxo_expiry_delta"] = serde_json::json!(42);
805
806 let info = serde_json::from_value::<ArkInfo>(json).unwrap();
807 assert_eq!(info.vtxo_lifetime, BlockDelta::new(42));
808 assert_eq!(info.vtxo_expiry_delta, BlockDelta::new(42));
809 }
810
811 #[test]
812 #[allow(deprecated)]
813 fn ark_info_vtxo_lifetime_kept_in_sync() {
814 let mut json = serde_json::to_value(ark_info_base()).unwrap();
815 json["vtxo_lifetime"] = serde_json::json!(42);
816 json["vtxo_expiry_delta"] = serde_json::json!(42);
817
818 let info = serde_json::from_value::<ArkInfo>(json).unwrap();
819 assert_eq!(info.vtxo_lifetime, BlockDelta::new(42));
820 assert_eq!(info.vtxo_expiry_delta, BlockDelta::new(42));
821
822 let json = serde_json::to_value(&info).unwrap();
824 assert_eq!(json["vtxo_lifetime"], 42);
825 assert_eq!(json["vtxo_expiry_delta"], 42);
826 }
827
828 #[test]
829 fn ark_info_vtxo_lifetime_rejects_diverging_fields() {
830 let mut json = serde_json::to_value(ark_info_base()).unwrap();
831 json["vtxo_lifetime"] = serde_json::json!(42);
832 json["vtxo_expiry_delta"] = serde_json::json!(100);
833
834 assert!(serde_json::from_value::<ArkInfo>(json).is_err());
835 }
836
837 #[test]
838 fn ark_info_fields() {
839 #[allow(unused, deprecated)]
843 fn convert(j: ArkInfo) -> ark::ArkInfo {
844 ark::ArkInfo {
845 network: j.network,
846 server_pubkey: j.server_pubkey,
847 mailbox_pubkey: j.mailbox_pubkey,
848 round_interval: j.round_interval,
849 nb_round_nonces: j.nb_round_nonces,
850 vtxo_exit_delta: j.vtxo_exit_delta,
851 vtxo_lifetime: j.vtxo_lifetime,
852 vtxo_expiry_delta: j.vtxo_expiry_delta,
853 htlc_send_expiry_delta: j.htlc_send_expiry_delta,
854 htlc_expiry_delta: j.htlc_expiry_delta,
855 max_vtxo_amount: j.max_vtxo_amount,
856 required_board_confirmations: j.required_board_confirmations,
857 max_user_invoice_cltv_delta: j.max_user_invoice_cltv_delta,
858 min_board_amount: j.min_board_amount,
859 offboard_feerate: FeeRate::from_sat_per_kwu(j.offboard_feerate_sat_per_kvb / 4),
860 ln_receive_anti_dos_required: j.ln_receive_anti_dos_required,
861 fees: j.fees.into(),
862 max_vtxo_exit_depth: j.max_vtxo_exit_depth,
863 max_offboard_inputs: j.max_offboard_inputs,
864 tos_link: j.tos_link,
865 }
866 }
867 }
868}
869