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::actions::lightning::receive::{
19 LightningReceive, LightningReceiveState, Progress as ReceiveProgress,
20};
21
22use crate::cli::fees::FeeSchedule;
23use crate::exit::error::ExitError;
24use crate::exit::package::ExitTransactionPackage;
25use crate::exit::ExitState;
26use crate::primitives::{TransactionInfo, WalletVtxoInfo};
27use crate::serde_utils;
28
29#[derive(Debug, Clone, Serialize)]
30#[cfg_attr(feature = "utoipa", derive(ToSchema))]
31pub struct ArkInfo {
32 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
34 pub network: bitcoin::Network,
35 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
37 pub server_pubkey: PublicKey,
38 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
40 pub mailbox_pubkey: PublicKey,
41 #[serde(with = "serde_utils::duration")]
43 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
44 pub round_interval: Duration,
45 pub nb_round_nonces: usize,
47 pub vtxo_exit_delta: BlockDelta,
49 #[serde(default)]
51 pub vtxo_lifetime: BlockDelta,
52 pub htlc_send_expiry_delta: BlockDelta,
54 pub htlc_expiry_delta: BlockDelta,
56 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
58 pub max_vtxo_amount: Option<Amount>,
59 pub required_board_confirmations: usize,
61 pub max_user_invoice_cltv_delta: u16,
64 #[serde(rename = "min_board_amount_sat", with = "bitcoin::amount::serde::as_sat")]
66 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
67 pub min_board_amount: Amount,
68 pub offboard_feerate_sat_per_kvb: u64,
70 pub ln_receive_anti_dos_required: bool,
74 pub fees: FeeSchedule,
76 pub max_vtxo_exit_depth: u16,
81 pub tos_link: Option<String>,
83 pub max_offboard_inputs: usize,
85
86 #[deprecated(note = "renamed to `vtxo_lifetime`")]
92 #[serde(default)]
93 #[cfg_attr(feature = "utoipa", schema(required = true))]
94 pub vtxo_expiry_delta: BlockDelta,
95}
96
97impl<'de> serde::Deserialize<'de> for ArkInfo {
98 #[allow(deprecated)]
99 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
100 #[derive(Deserialize)]
101 struct ArkInfoStub {
102 network: bitcoin::Network,
103 server_pubkey: PublicKey,
104 mailbox_pubkey: PublicKey,
105 #[serde(with = "serde_utils::duration")]
106 round_interval: Duration,
107 nb_round_nonces: usize,
108 vtxo_exit_delta: BlockDelta,
109 #[serde(default)]
110 vtxo_lifetime: BlockDelta,
111 htlc_send_expiry_delta: BlockDelta,
112 htlc_expiry_delta: BlockDelta,
113 max_vtxo_amount: Option<Amount>,
114 required_board_confirmations: usize,
115 max_user_invoice_cltv_delta: u16,
116 #[serde(rename = "min_board_amount_sat", with = "bitcoin::amount::serde::as_sat")]
117 min_board_amount: Amount,
118 offboard_feerate_sat_per_kvb: u64,
119 ln_receive_anti_dos_required: bool,
120 fees: FeeSchedule,
121 max_vtxo_exit_depth: u16,
122 tos_link: Option<String>,
123 max_offboard_inputs: usize,
124 #[serde(default)]
125 vtxo_expiry_delta: BlockDelta,
126 }
127
128 let v = ArkInfoStub::deserialize(d)?;
129
130 let vtxo_lifetime = match (v.vtxo_lifetime, v.vtxo_expiry_delta) {
131 (0, expiry) => expiry,
132 (lifetime, 0) => lifetime,
133 (lifetime, expiry) if lifetime == expiry => lifetime,
134 (lifetime, expiry) => return Err(serde::de::Error::custom(format!(
135 "vtxo_lifetime ({}) and vtxo_expiry_delta ({}) don't match", lifetime, expiry,
136 ))),
137 };
138
139 Ok(ArkInfo {
140 network: v.network,
141 server_pubkey: v.server_pubkey,
142 mailbox_pubkey: v.mailbox_pubkey,
143 round_interval: v.round_interval,
144 nb_round_nonces: v.nb_round_nonces,
145 vtxo_exit_delta: v.vtxo_exit_delta,
146 vtxo_lifetime: vtxo_lifetime,
147 vtxo_expiry_delta: vtxo_lifetime,
148 htlc_send_expiry_delta: v.htlc_send_expiry_delta,
149 htlc_expiry_delta: v.htlc_expiry_delta,
150 max_vtxo_amount: v.max_vtxo_amount,
151 required_board_confirmations: v.required_board_confirmations,
152 max_user_invoice_cltv_delta: v.max_user_invoice_cltv_delta,
153 min_board_amount: v.min_board_amount,
154 offboard_feerate_sat_per_kvb: v.offboard_feerate_sat_per_kvb,
155 ln_receive_anti_dos_required: v.ln_receive_anti_dos_required,
156 fees: v.fees,
157 max_vtxo_exit_depth: v.max_vtxo_exit_depth,
158 tos_link: v.tos_link,
159 max_offboard_inputs: v.max_offboard_inputs,
160 })
161 }
162}
163
164#[derive(Debug, Clone, Deserialize, Serialize)]
165#[cfg_attr(feature = "utoipa", derive(ToSchema))]
166pub struct NextRoundStart {
167 pub start_time: chrono::DateTime<chrono::Local>,
169}
170
171impl<T: Borrow<ark::ArkInfo>> From<T> for ArkInfo {
172 #[allow(deprecated)] fn from(v: T) -> Self {
174 let v = v.borrow();
175 ArkInfo {
176 network: v.network,
177 server_pubkey: v.server_pubkey,
178 mailbox_pubkey: v.mailbox_pubkey,
179 round_interval: v.round_interval,
180 nb_round_nonces: v.nb_round_nonces,
181 vtxo_exit_delta: v.vtxo_exit_delta,
182 vtxo_lifetime: v.vtxo_lifetime,
183 vtxo_expiry_delta: v.vtxo_lifetime,
186 htlc_send_expiry_delta: v.htlc_send_expiry_delta,
187 htlc_expiry_delta: v.htlc_expiry_delta,
188 max_vtxo_amount: v.max_vtxo_amount,
189 required_board_confirmations: v.required_board_confirmations,
190 max_user_invoice_cltv_delta: v.max_user_invoice_cltv_delta,
191 min_board_amount: v.min_board_amount,
192 offboard_feerate_sat_per_kvb: v.offboard_feerate.to_sat_per_kwu() * 4,
193 ln_receive_anti_dos_required: v.ln_receive_anti_dos_required,
194 fees: v.fees.clone().into(),
195 max_vtxo_exit_depth: v.max_vtxo_exit_depth,
196 max_offboard_inputs: v.max_offboard_inputs,
197 tos_link: v.tos_link.clone(),
198 }
199 }
200}
201
202#[derive(Debug, Clone, Deserialize, Serialize)]
204#[cfg_attr(feature = "utoipa", derive(ToSchema))]
205pub struct SignedMessage {
206 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
209 pub signature: schnorr::Signature,
210}
211
212#[derive(Debug, Clone, Deserialize, Serialize)]
214#[cfg_attr(feature = "utoipa", derive(ToSchema))]
215pub struct MessageVerification {
216 pub valid: bool,
218}
219
220#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
224#[cfg_attr(feature = "utoipa", derive(ToSchema))]
225pub struct Balance {
226 #[serde(rename = "spendable_sat", with = "bitcoin::amount::serde::as_sat")]
229 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
230 pub spendable: Amount,
231 #[serde(rename = "pending_lightning_send_sat", with = "bitcoin::amount::serde::as_sat")]
234 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
235 pub pending_lightning_send: Amount,
236 #[serde(rename = "claimable_lightning_receive_sat", with = "bitcoin::amount::serde::as_sat")]
239 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
240 pub claimable_lightning_receive: Amount,
241 #[serde(rename = "pending_in_round_sat", with = "bitcoin::amount::serde::as_sat")]
244 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
245 pub pending_in_round: Amount,
246 #[serde(rename = "pending_board_sat", with = "bitcoin::amount::serde::as_sat")]
249 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
250 pub pending_board: Amount,
251 #[serde(
256 default,
257 rename = "pending_exit_sat",
258 with = "bitcoin::amount::serde::as_sat::opt",
259 skip_serializing_if = "Option::is_none",
260 )]
261 #[cfg_attr(feature = "utoipa", schema(value_type = u64, nullable=true))]
262 pub pending_exit: Option<Amount>,
263}
264
265impl From<bark::Balance> for Balance {
266 fn from(v: bark::Balance) -> Self {
267 Balance {
268 spendable: v.spendable,
269 pending_in_round: v.pending_in_round,
270 pending_lightning_send: v.pending_lightning_send,
271 claimable_lightning_receive: v.claimable_lightning_receive,
272 pending_exit: v.pending_exit,
273 pending_board: v.pending_board,
274 }
275 }
276}
277
278#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
279#[cfg_attr(feature = "utoipa", derive(ToSchema))]
280pub struct ExitProgressResponse {
281 pub exits: Vec<ExitProgressStatus>,
283 pub done: bool,
285 pub claimable_height: Option<u32>,
287 #[serde(default, skip_serializing_if = "Option::is_none")]
292 pub error: Option<ExitError>,
293}
294
295#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
296#[cfg_attr(feature = "utoipa", derive(ToSchema))]
297pub struct ExitProgressStatus {
298 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
300 pub vtxo_id: VtxoId,
301 pub state: ExitState,
303 #[serde(default, skip_serializing_if = "Option::is_none")]
305 pub error: Option<ExitError>,
306}
307
308impl From<bark::exit::ExitProgressStatus> for ExitProgressStatus {
309 fn from(v: bark::exit::ExitProgressStatus) -> Self {
310 ExitProgressStatus {
311 vtxo_id: v.vtxo_id,
312 state: v.state.into(),
313 error: v.error.map(ExitError::from),
314 }
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
319#[cfg_attr(feature = "utoipa", derive(ToSchema))]
320pub struct ExitTransactionStatus {
321 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
323 pub vtxo_id: VtxoId,
324 pub state: ExitState,
326 #[serde(default, skip_serializing_if = "Option::is_none")]
328 pub history: Option<Vec<ExitState>>,
329 #[serde(default, skip_serializing_if = "Vec::is_empty")]
331 pub transactions: Vec<ExitTransactionPackage>,
332}
333
334impl From<bark::exit::ExitTransactionStatus> for ExitTransactionStatus {
335 fn from(v: bark::exit::ExitTransactionStatus) -> Self {
336 ExitTransactionStatus {
337 vtxo_id: v.vtxo_id,
338 state: v.state.into(),
339 history: v.history.map(|h| h.into_iter().map(ExitState::from).collect()),
340 transactions: v.transactions.into_iter().map(ExitTransactionPackage::from).collect(),
341 }
342 }
343}
344
345#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
347#[cfg_attr(feature = "utoipa", derive(ToSchema))]
348pub struct PendingBoardInfo {
349 pub funding_tx: TransactionInfo,
353 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
358 pub vtxos: Vec<VtxoId>,
359 #[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
361 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
362 pub amount: Amount,
363 pub movement_id: u32,
365}
366
367impl From<bark::persist::models::PendingBoard> for PendingBoardInfo {
368 fn from(v: bark::persist::models::PendingBoard) -> Self {
369 PendingBoardInfo {
370 funding_tx: v.funding_tx.into(),
371 vtxos: v.vtxos,
372 amount: v.amount,
373 movement_id: v.movement_id.0,
374 }
375 }
376}
377
378#[derive(Debug, Clone, Serialize, Deserialize)]
379#[serde(tag = "status", rename_all = "kebab-case")]
380#[cfg_attr(feature = "utoipa", derive(ToSchema))]
381pub enum RoundStatus {
382 SyncError {
384 error: String,
385 },
386 Confirmed {
388 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
389 funding_txid: Txid,
390 },
391 Unconfirmed {
393 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
394 funding_txid: Txid,
395 },
396 Pending,
398 Failed {
400 error: String,
401 },
402 Canceled,
404}
405
406impl RoundStatus {
407 pub fn is_final(&self) -> bool {
409 match self {
410 Self::SyncError { .. } => false,
411 Self::Confirmed { .. } => true,
412 Self::Unconfirmed { .. } => false,
413 Self::Pending { .. } => false,
414 Self::Failed { .. } => true,
415 Self::Canceled => true,
416 }
417 }
418
419 pub fn is_success(&self) -> bool {
421 match self {
422 Self::SyncError { .. } => false,
423 Self::Confirmed { .. } => true,
424 Self::Unconfirmed { .. } => true,
425 Self::Pending { .. } => false,
426 Self::Failed { .. } => false,
427 Self::Canceled => false,
428 }
429 }
430}
431
432impl From<bark::round::RoundStatus> for RoundStatus {
433 fn from(s: bark::round::RoundStatus) -> Self {
434 match s {
435 bark::round::RoundStatus::Confirmed { funding_txid } => {
436 Self::Confirmed { funding_txid }
437 },
438 bark::round::RoundStatus::Unconfirmed { funding_txid } => {
439 Self::Unconfirmed { funding_txid }
440 },
441 bark::round::RoundStatus::Pending => Self::Pending,
442 bark::round::RoundStatus::Failed { error } => Self::Failed { error },
443 bark::round::RoundStatus::Canceled => Self::Canceled,
444 }
445 }
446}
447
448#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
449#[cfg_attr(feature = "utoipa", derive(ToSchema))]
450pub struct RoundStateInfo {
451 pub round_state_id: u32,
452}
453
454#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
455#[cfg_attr(feature = "utoipa", derive(ToSchema))]
456pub struct InvoiceInfo {
457 pub invoice: String,
459}
460
461#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
462#[cfg_attr(feature = "utoipa", derive(ToSchema))]
463pub struct OffboardResult {
464 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
466 pub offboard_txid: Txid,
467}
468
469#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
470#[cfg_attr(feature = "utoipa", derive(ToSchema))]
471pub struct LightningReceiveInfo {
472 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
474 pub payment_hash: PaymentHash,
475 pub state: String,
478 pub invoice: String,
480 #[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
482 pub payment_preimage: Option<Preimage>,
483 #[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
485 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
486 pub amount: Amount,
487 #[serde(default, deserialize_with = "serde_utils::null_as_default")]
491 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>, required = true))]
492 pub htlc_vtxo_ids: Vec<VtxoId>,
493 pub settled_at: Option<chrono::DateTime<chrono::Local>>,
495
496 #[deprecated(note = "no longer tracked; use `state` and `settled_at`")]
498 #[serde(default)]
499 pub preimage_revealed_at: Option<chrono::DateTime<chrono::Local>>,
500 #[deprecated(note = "renamed to `settled_at`")]
502 #[serde(default)]
503 pub finished_at: Option<chrono::DateTime<chrono::Local>>,
504 #[deprecated(note = "replaced by `htlc_vtxo_ids`")]
506 #[serde(default, deserialize_with = "serde_utils::null_as_default")]
507 #[cfg_attr(feature = "utoipa", schema(required = true))]
508 pub htlc_vtxos: Vec<WalletVtxoInfo>,
509}
510
511impl LightningReceiveInfo {
512 #[allow(deprecated)] pub fn from_state(state: &LightningReceiveState) -> Self {
515 match state {
516 LightningReceiveState::InProgress(recv) => LightningReceiveInfo::from(recv),
517 LightningReceiveState::Settled(s) => LightningReceiveInfo {
518 payment_hash: s.payment_hash,
519 state: "settled".to_string(),
520 invoice: s.invoice.to_string(),
521 payment_preimage: Some(s.preimage),
522 amount: s.amount,
523 htlc_vtxo_ids: vec![],
524 settled_at: Some(s.settled_at),
525 preimage_revealed_at: None,
526 finished_at: Some(s.settled_at),
527 htlc_vtxos: vec![],
528 },
529 }
530 }
531}
532
533#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
534#[cfg_attr(feature = "utoipa", derive(ToSchema))]
535pub struct LightningSendInfo {
536 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
538 pub payment_hash: PaymentHash,
539 pub state: String,
542 pub invoice: Option<String>,
544 #[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
546 pub preimage: Option<Preimage>,
547}
548
549impl LightningSendInfo {
550 pub fn from_state(hash: PaymentHash, state: &LightningSendState) -> Self {
552 match state {
553 LightningSendState::Unknown => LightningSendInfo {
554 payment_hash: hash,
555 state: "unknown".to_string(),
556 invoice: None,
557 preimage: None,
558 },
559 LightningSendState::Paid(paid) => LightningSendInfo {
560 payment_hash: paid.payment_hash,
561 state: "paid".to_string(),
562 invoice: None,
563 preimage: Some(paid.preimage),
564 },
565 LightningSendState::InProgress(send) => {
566 let phase = match send.progress {
567 SendProgress::Start => "start",
568 SendProgress::HtlcReceived(_) => "htlc-received",
569 SendProgress::PaymentInitiated(_) => "payment-initiated",
570 SendProgress::RevocableHtlcs { .. } => "revocable-htlcs",
571 SendProgress::RevocationStuck { .. } => "revocation-stuck",
572 };
573 LightningSendInfo {
574 payment_hash: send.invoice.payment_hash(),
575 state: phase.to_string(),
576 invoice: Some(send.invoice.to_string()),
577 preimage: None,
578 }
579 },
580 }
581 }
582}
583
584impl From<&LightningReceive> for LightningReceiveInfo {
585 #[allow(deprecated)] fn from(recv: &LightningReceive) -> Self {
587 let (state, htlc_vtxo_ids) = match &recv.progress {
588 ReceiveProgress::AwaitingPayment => ("awaiting-payment", vec![]),
589 ReceiveProgress::HtlcsReady(htlcs) => ("htlcs-ready", htlcs.vtxo_ids.clone()),
590 ReceiveProgress::PreimageRevealed(htlcs) => ("preimage-revealed", htlcs.vtxo_ids.clone()),
591 ReceiveProgress::Delivering(_) => ("delivering", vec![]),
593 };
594 LightningReceiveInfo {
595 payment_hash: recv.payment_hash,
596 state: state.to_string(),
597 invoice: recv.invoice.to_string(),
598 payment_preimage: Some(recv.payment_preimage),
599 amount: recv.invoice.amount_milli_satoshis()
600 .map(Amount::from_msat_floor)
601 .expect("generated invoice with no amount"),
602 htlc_vtxo_ids,
603 settled_at: None,
604 preimage_revealed_at: None,
605 finished_at: None,
606 htlc_vtxos: vec![],
607 }
608 }
609}
610
611#[cfg(test)]
612mod test {
613 use bitcoin::FeeRate;
614 use super::*;
615
616 fn lightning_receive_base_json() -> serde_json::Value {
617 serde_json::json!({
618 "amount_sat": 1000,
619 "payment_hash": "0000000000000000000000000000000000000000000000000000000000000000",
620 "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000",
621 "state": "awaiting-payment",
622 "settled_at": null,
623 "invoice": "lnbc1",
624 })
625 }
626
627 #[test]
628 fn deserialize_lightning_receive_htlc_vtxo_ids_missing() {
629 let json = lightning_receive_base_json();
630 serde_json::from_value::<LightningReceiveInfo>(json).unwrap();
631 }
632
633 #[test]
634 fn deserialize_lightning_receive_htlc_vtxo_ids_null() {
635 let mut json = lightning_receive_base_json();
636 json["htlc_vtxo_ids"] = serde_json::json!(null);
637 serde_json::from_value::<LightningReceiveInfo>(json).unwrap();
638 }
639
640 #[test]
641 fn deserialize_lightning_receive_htlc_vtxo_ids_empty() {
642 let mut json = lightning_receive_base_json();
643 json["htlc_vtxo_ids"] = serde_json::json!([]);
644 serde_json::from_value::<LightningReceiveInfo>(json).unwrap();
645 }
646
647 #[allow(deprecated)]
648 fn ark_info_base() -> ArkInfo {
649 let pubkey = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
650 .parse::<PublicKey>().unwrap();
651 ArkInfo {
652 network: bitcoin::Network::Regtest,
653 server_pubkey: pubkey,
654 mailbox_pubkey: pubkey,
655 round_interval: Duration::from_secs(60),
656 nb_round_nonces: 1,
657 vtxo_exit_delta: 12,
658 vtxo_lifetime: 100,
659 vtxo_expiry_delta: 100,
660 htlc_send_expiry_delta: 100,
661 htlc_expiry_delta: 6,
662 max_vtxo_amount: None,
663 required_board_confirmations: 3,
664 max_user_invoice_cltv_delta: 100,
665 min_board_amount: Amount::from_sat(1000),
666 offboard_feerate_sat_per_kvb: 1000,
667 ln_receive_anti_dos_required: false,
668 fees: ark::fees::FeeSchedule::default().into(),
669 max_vtxo_exit_depth: 10,
670 tos_link: None,
671 max_offboard_inputs: 4,
672 }
673 }
674
675 #[test]
676 #[allow(deprecated)]
677 fn ark_info_vtxo_lifetime_falls_back_to_deprecated_field() {
678 let mut json = serde_json::to_value(ark_info_base()).unwrap();
680 json.as_object_mut().unwrap().remove("vtxo_lifetime");
681 json["vtxo_expiry_delta"] = serde_json::json!(42);
682
683 let info = serde_json::from_value::<ArkInfo>(json).unwrap();
684 assert_eq!(info.vtxo_lifetime, 42);
685 assert_eq!(info.vtxo_expiry_delta, 42);
686 }
687
688 #[test]
689 #[allow(deprecated)]
690 fn ark_info_vtxo_lifetime_kept_in_sync() {
691 let mut json = serde_json::to_value(ark_info_base()).unwrap();
692 json["vtxo_lifetime"] = serde_json::json!(42);
693 json["vtxo_expiry_delta"] = serde_json::json!(42);
694
695 let info = serde_json::from_value::<ArkInfo>(json).unwrap();
696 assert_eq!(info.vtxo_lifetime, 42);
697 assert_eq!(info.vtxo_expiry_delta, 42);
698
699 let json = serde_json::to_value(&info).unwrap();
701 assert_eq!(json["vtxo_lifetime"], 42);
702 assert_eq!(json["vtxo_expiry_delta"], 42);
703 }
704
705 #[test]
706 fn ark_info_vtxo_lifetime_rejects_diverging_fields() {
707 let mut json = serde_json::to_value(ark_info_base()).unwrap();
708 json["vtxo_lifetime"] = serde_json::json!(42);
709 json["vtxo_expiry_delta"] = serde_json::json!(100);
710
711 assert!(serde_json::from_value::<ArkInfo>(json).is_err());
712 }
713
714 #[test]
715 fn ark_info_fields() {
716 #[allow(unused, deprecated)]
720 fn convert(j: ArkInfo) -> ark::ArkInfo {
721 ark::ArkInfo {
722 network: j.network,
723 server_pubkey: j.server_pubkey,
724 mailbox_pubkey: j.mailbox_pubkey,
725 round_interval: j.round_interval,
726 nb_round_nonces: j.nb_round_nonces,
727 vtxo_exit_delta: j.vtxo_exit_delta,
728 vtxo_lifetime: j.vtxo_lifetime,
729 vtxo_expiry_delta: j.vtxo_expiry_delta,
730 htlc_send_expiry_delta: j.htlc_send_expiry_delta,
731 htlc_expiry_delta: j.htlc_expiry_delta,
732 max_vtxo_amount: j.max_vtxo_amount,
733 required_board_confirmations: j.required_board_confirmations,
734 max_user_invoice_cltv_delta: j.max_user_invoice_cltv_delta,
735 min_board_amount: j.min_board_amount,
736 offboard_feerate: FeeRate::from_sat_per_kwu(j.offboard_feerate_sat_per_kvb / 4),
737 ln_receive_anti_dos_required: j.ln_receive_anti_dos_required,
738 fees: j.fees.into(),
739 max_vtxo_exit_depth: j.max_vtxo_exit_depth,
740 max_offboard_inputs: j.max_offboard_inputs,
741 tos_link: j.tos_link,
742 }
743 }
744 }
745}
746