1pub mod fees;
2#[cfg(feature = "onchain-bdk")]
3pub mod onchain;
4
5use std::borrow::Borrow;
6use std::collections::HashMap;
7use std::str::FromStr;
8use std::time::Duration;
9
10use anyhow::anyhow;
11use bitcoin::secp256k1::PublicKey;
12use bitcoin::{Amount, Txid, SignedAmount, ScriptBuf};
13use chrono::DateTime;
14#[cfg(feature = "utoipa")]
15use utoipa::ToSchema;
16
17use ark::VtxoId;
18use ark::lightning::{Invoice, Offer, PaymentHash, Preimage};
19use bark::lnurllib::lightning_address::LightningAddress;
20use bark::movement::MovementId;
21use bitcoin_ext::{AmountExt, BlockDelta};
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, Deserialize, 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 pub vtxo_exit_delta: BlockDelta,
50 pub vtxo_expiry_delta: 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}
77
78#[derive(Debug, Clone, Deserialize, Serialize)]
79#[cfg_attr(feature = "utoipa", derive(ToSchema))]
80pub struct NextRoundStart {
81 pub start_time: chrono::DateTime<chrono::Local>,
83}
84
85impl<T: Borrow<ark::ArkInfo>> From<T> for ArkInfo {
86 fn from(v: T) -> Self {
87 let v = v.borrow();
88 ArkInfo {
89 network: v.network,
90 server_pubkey: v.server_pubkey,
91 mailbox_pubkey: v.mailbox_pubkey,
92 round_interval: v.round_interval,
93 nb_round_nonces: v.nb_round_nonces,
94 vtxo_exit_delta: v.vtxo_exit_delta,
95 vtxo_expiry_delta: v.vtxo_expiry_delta,
96 htlc_send_expiry_delta: v.htlc_send_expiry_delta,
97 htlc_expiry_delta: v.htlc_expiry_delta,
98 max_vtxo_amount: v.max_vtxo_amount,
99 required_board_confirmations: v.required_board_confirmations,
100 max_user_invoice_cltv_delta: v.max_user_invoice_cltv_delta,
101 min_board_amount: v.min_board_amount,
102 offboard_feerate_sat_per_kvb: v.offboard_feerate.to_sat_per_kwu() * 4,
103 ln_receive_anti_dos_required: v.ln_receive_anti_dos_required,
104 fees: v.fees.clone().into(),
105 }
106 }
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
113#[cfg_attr(feature = "utoipa", derive(ToSchema))]
114pub struct Balance {
115 #[serde(rename = "spendable_sat", with = "bitcoin::amount::serde::as_sat")]
118 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
119 pub spendable: Amount,
120 #[serde(rename = "pending_lightning_send_sat", with = "bitcoin::amount::serde::as_sat")]
123 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
124 pub pending_lightning_send: Amount,
125 #[serde(rename = "claimable_lightning_receive_sat", with = "bitcoin::amount::serde::as_sat")]
128 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
129 pub claimable_lightning_receive: Amount,
130 #[serde(rename = "pending_in_round_sat", with = "bitcoin::amount::serde::as_sat")]
133 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
134 pub pending_in_round: Amount,
135 #[serde(rename = "pending_board_sat", with = "bitcoin::amount::serde::as_sat")]
138 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
139 pub pending_board: Amount,
140 #[serde(
143 default,
144 rename = "pending_exit_sat",
145 with = "bitcoin::amount::serde::as_sat::opt",
146 skip_serializing_if = "Option::is_none",
147 )]
148 #[cfg_attr(feature = "utoipa", schema(value_type = u64, nullable=true))]
149 pub pending_exit: Option<Amount>,
150}
151
152impl From<bark::Balance> for Balance {
153 fn from(v: bark::Balance) -> Self {
154 Balance {
155 spendable: v.spendable,
156 pending_in_round: v.pending_in_round,
157 pending_lightning_send: v.pending_lightning_send,
158 claimable_lightning_receive: v.claimable_lightning_receive,
159 pending_exit: v.pending_exit,
160 pending_board: v.pending_board,
161 }
162 }
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
166#[cfg_attr(feature = "utoipa", derive(ToSchema))]
167pub struct ExitProgressResponse {
168 pub exits: Vec<ExitProgressStatus>,
170 pub done: bool,
172 pub claimable_height: Option<u32>,
174}
175
176#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
177#[cfg_attr(feature = "utoipa", derive(ToSchema))]
178pub struct ExitProgressStatus {
179 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
181 pub vtxo_id: VtxoId,
182 pub state: ExitState,
184 #[serde(default, skip_serializing_if = "Option::is_none")]
186 pub error: Option<ExitError>,
187}
188
189impl From<bark::exit::ExitProgressStatus> for ExitProgressStatus {
190 fn from(v: bark::exit::ExitProgressStatus) -> Self {
191 ExitProgressStatus {
192 vtxo_id: v.vtxo_id,
193 state: v.state.into(),
194 error: v.error.map(ExitError::from),
195 }
196 }
197}
198
199#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
200#[cfg_attr(feature = "utoipa", derive(ToSchema))]
201pub struct ExitTransactionStatus {
202 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
204 pub vtxo_id: VtxoId,
205 pub state: ExitState,
207 #[serde(default, skip_serializing_if = "Option::is_none")]
209 pub history: Option<Vec<ExitState>>,
210 #[serde(default, skip_serializing_if = "Vec::is_empty")]
212 pub transactions: Vec<ExitTransactionPackage>,
213}
214
215impl From<bark::exit::ExitTransactionStatus> for ExitTransactionStatus {
216 fn from(v: bark::exit::ExitTransactionStatus) -> Self {
217 ExitTransactionStatus {
218 vtxo_id: v.vtxo_id,
219 state: v.state.into(),
220 history: v.history.map(|h| h.into_iter().map(ExitState::from).collect()),
221 transactions: v.transactions.into_iter().map(ExitTransactionPackage::from).collect(),
222 }
223 }
224}
225
226#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
228#[cfg_attr(feature = "utoipa", derive(ToSchema))]
229pub struct PendingBoardInfo {
230 pub funding_tx: TransactionInfo,
234 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
239 pub vtxos: Vec<VtxoId>,
240 #[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
242 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
243 pub amount: Amount,
244 pub movement_id: u32,
246}
247
248impl From<bark::persist::models::PendingBoard> for PendingBoardInfo {
249 fn from(v: bark::persist::models::PendingBoard) -> Self {
250 PendingBoardInfo {
251 funding_tx: v.funding_tx.into(),
252 vtxos: v.vtxos,
253 amount: v.amount,
254 movement_id: v.movement_id.0,
255 }
256 }
257}
258
259#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
260#[serde(rename_all = "kebab-case")]
261#[cfg_attr(feature = "utoipa", derive(ToSchema))]
262pub enum MovementStatus {
263 Pending,
265 Successful,
267 Failed,
270 Canceled,
273}
274
275impl From<bark::movement::MovementStatus> for MovementStatus {
276 fn from(v: bark::movement::MovementStatus) -> Self {
277 match v {
278 bark::movement::MovementStatus::Pending => Self::Pending,
279 bark::movement::MovementStatus::Successful => Self::Successful,
280 bark::movement::MovementStatus::Failed => Self::Failed,
281 bark::movement::MovementStatus::Canceled => Self::Canceled,
282 }
283 }
284}
285
286#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
288#[cfg_attr(feature = "utoipa", derive(ToSchema))]
289pub struct Movement {
290 #[cfg_attr(feature = "utoipa", schema(value_type = u32))]
292 pub id: MovementId,
293 pub status: MovementStatus,
295 pub subsystem: MovementSubsystem,
298 #[serde(default, skip_serializing_if = "Option::is_none")]
301 pub metadata: Option<HashMap<String, serde_json::Value>>,
302 #[serde(rename="intended_balance_sat", with="bitcoin::amount::serde::as_sat")]
305 #[cfg_attr(feature = "utoipa", schema(value_type = i64))]
306 pub intended_balance: SignedAmount,
307 #[serde(rename="effective_balance_sat", with="bitcoin::amount::serde::as_sat")]
311 #[cfg_attr(feature = "utoipa", schema(value_type = i64))]
312 pub effective_balance: SignedAmount,
313 #[serde(rename="offchain_fee_sat", with="bitcoin::amount::serde::as_sat")]
317 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
318 pub offchain_fee: Amount,
319 pub sent_to: Vec<MovementDestination>,
321 pub received_on: Vec<MovementDestination>,
324 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
327 pub input_vtxos: Vec<VtxoId>,
328 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
331 pub output_vtxos: Vec<VtxoId>,
332 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
337 pub exited_vtxos: Vec<VtxoId>,
338 pub time: MovementTimestamp,
340}
341
342impl From<bark::movement::Movement> for Movement {
343 fn from(m: bark::movement::Movement) -> Self {
344 Movement {
345 id: m.id,
346 status: m.status.into(),
347 subsystem: MovementSubsystem::from(m.subsystem),
348 metadata: if m.metadata.is_empty() { None } else { Some(m.metadata) },
349 intended_balance: m.intended_balance,
350 effective_balance: m.effective_balance,
351 offchain_fee: m.offchain_fee,
352 sent_to: m.sent_to.into_iter().map(MovementDestination::from).collect(),
353 received_on: m.received_on.into_iter().map(MovementDestination::from).collect(),
354 input_vtxos: m.input_vtxos,
355 output_vtxos: m.output_vtxos,
356 exited_vtxos: m.exited_vtxos,
357 time: MovementTimestamp::from(m.time),
358 }
359 }
360}
361
362#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
365#[cfg_attr(feature = "utoipa", derive(ToSchema))]
366pub struct MovementDestination {
367 pub destination: PaymentMethod,
369 #[serde(rename="amount_sat", with="bitcoin::amount::serde::as_sat")]
371 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
372 pub amount: Amount,
373}
374
375impl From<bark::movement::MovementDestination> for MovementDestination {
376 fn from(d: bark::movement::MovementDestination) -> Self {
377 MovementDestination {
378 destination: PaymentMethod::from(d.destination),
379 amount: d.amount,
380 }
381 }
382}
383
384#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
388#[serde(tag = "type", content = "value", rename_all = "kebab-case")]
389pub enum PaymentMethod {
390 Ark(String),
392 Bitcoin(String),
394 OutputScript(String),
397 Invoice(String),
399 Offer(String),
401 LightningAddress(String),
403 Custom(String),
405}
406
407#[cfg(feature = "utoipa")]
408impl utoipa::PartialSchema for PaymentMethod {
409 fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
410 use utoipa::openapi::schema;
411
412 schema::ObjectBuilder::new()
413 .title(Some("PaymentMethod"))
414 .description(Some("A payment method with a type discriminator and string value"))
415 .property(
416 "type",
417 schema::ObjectBuilder::new()
418 .schema_type(schema::SchemaType::Type(schema::Type::String))
419 .enum_values(Some([
420 "ark",
421 "bitcoin",
422 "output-script",
423 "invoice",
424 "offer",
425 "lightning-address",
426 "custom",
427 ]))
428 .description(Some("The type of payment method"))
429 )
430 .required("type")
431 .property(
432 "value",
433 schema::ObjectBuilder::new()
434 .schema_type(schema::SchemaType::Type(schema::Type::String))
435 .description(Some("The payment method value (address, invoice, etc.)"))
436 )
437 .required("value")
438 .into()
439 }
440}
441
442#[cfg(feature = "utoipa")]
443impl utoipa::ToSchema for PaymentMethod {
444 fn name() -> std::borrow::Cow<'static, str> {
445 std::borrow::Cow::Borrowed("PaymentMethod")
446 }
447}
448
449impl From<bark::movement::PaymentMethod> for PaymentMethod {
450 fn from(p: bark::movement::PaymentMethod) -> Self {
451 match p {
452 bark::movement::PaymentMethod::Ark(a) => Self::Ark(a.to_string()),
453 bark::movement::PaymentMethod::Bitcoin(b) => Self::Bitcoin(b.assume_checked().to_string()),
454 bark::movement::PaymentMethod::OutputScript(s) => Self::OutputScript(s.to_hex_string()),
455 bark::movement::PaymentMethod::Invoice(i) => Self::Invoice(i.to_string()),
456 bark::movement::PaymentMethod::Offer(o) => Self::Offer(o.to_string()),
457 bark::movement::PaymentMethod::LightningAddress(l) => Self::LightningAddress(l.to_string()),
458 bark::movement::PaymentMethod::Custom(c) => Self::Custom(c),
459 }
460 }
461}
462
463impl TryFrom<PaymentMethod> for bark::movement::PaymentMethod {
464 type Error = anyhow::Error;
465
466 fn try_from(p: PaymentMethod) -> Result<Self, Self::Error> {
467 match p {
468 PaymentMethod::Ark(a) => Ok(bark::movement::PaymentMethod::Ark(
469 ark::Address::from_str(&a)?,
470 )),
471 PaymentMethod::Bitcoin(b) => Ok(bark::movement::PaymentMethod::Bitcoin(
472 bitcoin::Address::from_str(&b)?,
473 )),
474 PaymentMethod::OutputScript(s) => Ok(bark::movement::PaymentMethod::OutputScript(
475 ScriptBuf::from_hex(&s)?,
476 )),
477 PaymentMethod::Invoice(i) => Ok(bark::movement::PaymentMethod::Invoice(
478 Invoice::from_str(&i)?,
479 )),
480 PaymentMethod::Offer(o) => Ok(bark::movement::PaymentMethod::Offer(
481 Offer::from_str(&o).map_err(|e| anyhow!("Failed to parse offer: {:?}", e))?,
482 )),
483 PaymentMethod::LightningAddress(l) => Ok(bark::movement::PaymentMethod::LightningAddress(
484 LightningAddress::from_str(&l)?,
485 )),
486 PaymentMethod::Custom(c) => Ok(bark::movement::PaymentMethod::Custom(c)),
487 }
488 }
489}
490
491#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
494#[cfg_attr(feature = "utoipa", derive(ToSchema))]
495pub struct MovementSubsystem {
496 pub name: String,
498 pub kind: String,
500}
501
502impl From<bark::movement::MovementSubsystem> for MovementSubsystem {
503 fn from(s: bark::movement::MovementSubsystem) -> Self {
504 MovementSubsystem {
505 name: s.name,
506 kind: s.kind,
507 }
508 }
509}
510
511#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
513#[cfg_attr(feature = "utoipa", derive(ToSchema))]
514pub struct MovementTimestamp {
515 pub created_at: DateTime<chrono::Local>,
517 pub updated_at: DateTime<chrono::Local>,
519 #[serde(default, skip_serializing_if = "Option::is_none")]
521 pub completed_at: Option<DateTime<chrono::Local>>,
522}
523
524impl From<bark::movement::MovementTimestamp> for MovementTimestamp {
525 fn from(t: bark::movement::MovementTimestamp) -> Self {
526 MovementTimestamp {
527 created_at: t.created_at,
528 updated_at: t.updated_at,
529 completed_at: t.completed_at,
530 }
531 }
532}
533
534#[derive(Debug, Clone, Serialize, Deserialize)]
535#[serde(tag = "status", rename_all = "kebab-case")]
536#[cfg_attr(feature = "utoipa", derive(ToSchema))]
537pub enum RoundStatus {
538 SyncError {
540 error: String,
541 },
542 Confirmed {
544 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
545 funding_txid: Txid,
546 },
547 Unconfirmed {
549 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
550 funding_txid: Txid,
551 },
552 Pending,
554 Failed {
556 error: String,
557 },
558 Canceled,
560}
561
562impl RoundStatus {
563 pub fn is_final(&self) -> bool {
565 match self {
566 Self::SyncError { .. } => false,
567 Self::Confirmed { .. } => true,
568 Self::Unconfirmed { .. } => false,
569 Self::Pending { .. } => false,
570 Self::Failed { .. } => true,
571 Self::Canceled => true,
572 }
573 }
574
575 pub fn is_success(&self) -> bool {
577 match self {
578 Self::SyncError { .. } => false,
579 Self::Confirmed { .. } => true,
580 Self::Unconfirmed { .. } => true,
581 Self::Pending { .. } => false,
582 Self::Failed { .. } => false,
583 Self::Canceled => false,
584 }
585 }
586}
587
588impl From<bark::round::RoundStatus> for RoundStatus {
589 fn from(s: bark::round::RoundStatus) -> Self {
590 match s {
591 bark::round::RoundStatus::Confirmed { funding_txid } => {
592 Self::Confirmed { funding_txid }
593 },
594 bark::round::RoundStatus::Unconfirmed { funding_txid } => {
595 Self::Unconfirmed { funding_txid }
596 },
597 bark::round::RoundStatus::Pending => Self::Pending,
598 bark::round::RoundStatus::Failed { error } => Self::Failed { error },
599 bark::round::RoundStatus::Canceled => Self::Canceled,
600 }
601 }
602}
603
604#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
605#[cfg_attr(feature = "utoipa", derive(ToSchema))]
606pub struct RoundStateInfo {
607 pub round_state_id: u32,
608}
609
610#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
611#[cfg_attr(feature = "utoipa", derive(ToSchema))]
612pub struct InvoiceInfo {
613 pub invoice: String,
615}
616
617#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
618#[cfg_attr(feature = "utoipa", derive(ToSchema))]
619pub struct OffboardResult {
620 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
622 pub offboard_txid: Txid,
623}
624
625#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
626#[cfg_attr(feature = "utoipa", derive(ToSchema))]
627pub struct LightningReceiveInfo {
628 #[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
630 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
631 pub amount: Amount,
632 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
634 pub payment_hash: PaymentHash,
635 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
637 pub payment_preimage: Preimage,
638 pub preimage_revealed_at: Option<chrono::DateTime<chrono::Local>>,
640 pub finished_at: Option<chrono::DateTime<chrono::Local>>,
642 pub invoice: String,
644 #[serde(default, deserialize_with = "serde_utils::null_as_default")]
648 #[cfg_attr(feature = "utoipa", schema(required = true))]
649 pub htlc_vtxos: Vec<WalletVtxoInfo>,
650}
651
652impl From<bark::persist::models::LightningReceive> for LightningReceiveInfo {
653 fn from(v: bark::persist::models::LightningReceive) -> Self {
654 LightningReceiveInfo {
655 payment_hash: v.payment_hash,
656 payment_preimage: v.payment_preimage,
657 preimage_revealed_at: v.preimage_revealed_at,
658 invoice: v.invoice.to_string(),
659 htlc_vtxos: v.htlc_vtxos.into_iter()
660 .map(crate::primitives::WalletVtxoInfo::from).collect(),
661 amount: v.invoice.amount_milli_satoshis().map(Amount::from_msat_floor)
662 .unwrap_or(Amount::ZERO),
663 finished_at: v.finished_at,
664 }
665 }
666}
667
668#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
669#[cfg_attr(feature = "utoipa", derive(ToSchema))]
670pub struct LightningSendInfo {
671 #[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
673 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
674 pub amount: Amount,
675 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
677 pub payment_hash: PaymentHash,
678 pub invoice: String,
680 #[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
682 pub preimage: Option<Preimage>,
683 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<WalletVtxoInfo>))]
685 pub htlc_vtxos: Vec<WalletVtxoInfo>,
686 #[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
688 pub finished_at: Option<chrono::DateTime<chrono::Local>>,
689}
690
691impl From<bark::persist::models::LightningSend> for LightningSendInfo {
692 fn from(v: bark::persist::models::LightningSend) -> Self {
693 LightningSendInfo {
694 payment_hash: v.invoice.payment_hash(),
695 invoice: v.invoice.to_string(),
696 htlc_vtxos: v.htlc_vtxos.into_iter()
697 .map(crate::primitives::WalletVtxoInfo::from).collect(),
698 amount: v.amount,
699 preimage: v.preimage,
700 finished_at: v.finished_at,
701 }
702 }
703}
704
705#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
707#[serde(tag = "status", rename_all = "kebab-case")]
708#[cfg_attr(feature = "utoipa", derive(ToSchema))]
709pub enum LightningMovement {
710 Receive(LightningReceiveInfo),
712 Send(LightningSendInfo),
714}
715
716#[cfg(test)]
717mod test {
718 use bitcoin::FeeRate;
719 use super::*;
720
721 fn lightning_receive_base_json() -> serde_json::Value {
722 serde_json::json!({
723 "amount_sat": 1000,
724 "payment_hash": "0000000000000000000000000000000000000000000000000000000000000000",
725 "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000",
726 "preimage_revealed_at": null,
727 "finished_at": null,
728 "invoice": "lnbc1",
729 })
730 }
731
732 #[test]
733 fn deserialize_lightning_receive_htlc_vtxos_missing() {
734 let json = lightning_receive_base_json();
735 serde_json::from_value::<LightningReceiveInfo>(json).unwrap();
736 }
737
738 #[test]
739 fn deserialize_lightning_receive_htlc_vtxos_null() {
740 let mut json = lightning_receive_base_json();
741 json["htlc_vtxos"] = serde_json::json!(null);
742 serde_json::from_value::<LightningReceiveInfo>(json).unwrap();
743 }
744
745 #[test]
746 fn deserialize_lightning_receive_htlc_vtxos_empty() {
747 let mut json = lightning_receive_base_json();
748 json["htlc_vtxos"] = serde_json::json!([]);
749 serde_json::from_value::<LightningReceiveInfo>(json).unwrap();
750 }
751
752 #[test]
753 fn ark_info_fields() {
754 #[allow(unused)]
758 fn convert(j: ArkInfo) -> ark::ArkInfo {
759 ark::ArkInfo {
760 network: j.network,
761 server_pubkey: j.server_pubkey,
762 mailbox_pubkey: j.mailbox_pubkey,
763 round_interval: j.round_interval,
764 nb_round_nonces: j.nb_round_nonces,
765 vtxo_exit_delta: j.vtxo_exit_delta,
766 vtxo_expiry_delta: j.vtxo_expiry_delta,
767 htlc_send_expiry_delta: j.htlc_send_expiry_delta,
768 htlc_expiry_delta: j.htlc_expiry_delta,
769 max_vtxo_amount: j.max_vtxo_amount,
770 required_board_confirmations: j.required_board_confirmations,
771 max_user_invoice_cltv_delta: j.max_user_invoice_cltv_delta,
772 min_board_amount: j.min_board_amount,
773 offboard_feerate: FeeRate::from_sat_per_kwu(j.offboard_feerate_sat_per_kvb / 4),
774 ln_receive_anti_dos_required: j.ln_receive_anti_dos_required,
775 fees: j.fees.into(),
776 }
777 }
778 }
779}
780