1pub mod fees;
2#[cfg(feature = "onchain-bdk")]
3pub mod onchain;
4
5use std::borrow::Borrow;
6use std::time::Duration;
7
8use bitcoin::secp256k1::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 crate::cli::fees::FeeSchedule;
18use crate::exit::error::ExitError;
19use crate::exit::package::ExitTransactionPackage;
20use crate::exit::ExitState;
21use crate::primitives::{TransactionInfo, WalletVtxoInfo};
22use crate::serde_utils;
23
24#[derive(Debug, Clone, Deserialize, Serialize)]
25#[cfg_attr(feature = "utoipa", derive(ToSchema))]
26pub struct ArkInfo {
27 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
29 pub network: bitcoin::Network,
30 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
32 pub server_pubkey: PublicKey,
33 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
35 pub mailbox_pubkey: PublicKey,
36 #[serde(with = "serde_utils::duration")]
38 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
39 pub round_interval: Duration,
40 pub nb_round_nonces: usize,
42 pub vtxo_exit_delta: BlockDelta,
44 pub vtxo_expiry_delta: BlockDelta,
46 pub htlc_send_expiry_delta: BlockDelta,
48 pub htlc_expiry_delta: BlockDelta,
50 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
52 pub max_vtxo_amount: Option<Amount>,
53 pub required_board_confirmations: usize,
55 pub max_user_invoice_cltv_delta: u16,
58 #[serde(rename = "min_board_amount_sat", with = "bitcoin::amount::serde::as_sat")]
60 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
61 pub min_board_amount: Amount,
62 pub offboard_feerate_sat_per_kvb: u64,
64 pub ln_receive_anti_dos_required: bool,
68 pub fees: FeeSchedule,
70 pub max_vtxo_exit_depth: u16,
75}
76
77#[derive(Debug, Clone, Deserialize, Serialize)]
78#[cfg_attr(feature = "utoipa", derive(ToSchema))]
79pub struct NextRoundStart {
80 pub start_time: chrono::DateTime<chrono::Local>,
82}
83
84impl<T: Borrow<ark::ArkInfo>> From<T> for ArkInfo {
85 #[allow(deprecated)] 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 max_vtxo_exit_depth: v.max_vtxo_exit_depth,
106 }
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
114#[cfg_attr(feature = "utoipa", derive(ToSchema))]
115pub struct Balance {
116 #[serde(rename = "spendable_sat", with = "bitcoin::amount::serde::as_sat")]
119 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
120 pub spendable: Amount,
121 #[serde(rename = "pending_lightning_send_sat", with = "bitcoin::amount::serde::as_sat")]
124 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
125 pub pending_lightning_send: Amount,
126 #[serde(rename = "claimable_lightning_receive_sat", with = "bitcoin::amount::serde::as_sat")]
129 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
130 pub claimable_lightning_receive: Amount,
131 #[serde(rename = "pending_in_round_sat", with = "bitcoin::amount::serde::as_sat")]
134 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
135 pub pending_in_round: Amount,
136 #[serde(rename = "pending_board_sat", with = "bitcoin::amount::serde::as_sat")]
139 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
140 pub pending_board: Amount,
141 #[serde(
144 default,
145 rename = "pending_exit_sat",
146 with = "bitcoin::amount::serde::as_sat::opt",
147 skip_serializing_if = "Option::is_none",
148 )]
149 #[cfg_attr(feature = "utoipa", schema(value_type = u64, nullable=true))]
150 pub pending_exit: Option<Amount>,
151}
152
153impl From<bark::Balance> for Balance {
154 fn from(v: bark::Balance) -> Self {
155 Balance {
156 spendable: v.spendable,
157 pending_in_round: v.pending_in_round,
158 pending_lightning_send: v.pending_lightning_send,
159 claimable_lightning_receive: v.claimable_lightning_receive,
160 pending_exit: v.pending_exit,
161 pending_board: v.pending_board,
162 }
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
167#[cfg_attr(feature = "utoipa", derive(ToSchema))]
168pub struct ExitProgressResponse {
169 pub exits: Vec<ExitProgressStatus>,
171 pub done: bool,
173 pub claimable_height: Option<u32>,
175}
176
177#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
178#[cfg_attr(feature = "utoipa", derive(ToSchema))]
179pub struct ExitProgressStatus {
180 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
182 pub vtxo_id: VtxoId,
183 pub state: ExitState,
185 #[serde(default, skip_serializing_if = "Option::is_none")]
187 pub error: Option<ExitError>,
188}
189
190impl From<bark::exit::ExitProgressStatus> for ExitProgressStatus {
191 fn from(v: bark::exit::ExitProgressStatus) -> Self {
192 ExitProgressStatus {
193 vtxo_id: v.vtxo_id,
194 state: v.state.into(),
195 error: v.error.map(ExitError::from),
196 }
197 }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
201#[cfg_attr(feature = "utoipa", derive(ToSchema))]
202pub struct ExitTransactionStatus {
203 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
205 pub vtxo_id: VtxoId,
206 pub state: ExitState,
208 #[serde(default, skip_serializing_if = "Option::is_none")]
210 pub history: Option<Vec<ExitState>>,
211 #[serde(default, skip_serializing_if = "Vec::is_empty")]
213 pub transactions: Vec<ExitTransactionPackage>,
214}
215
216impl From<bark::exit::ExitTransactionStatus> for ExitTransactionStatus {
217 fn from(v: bark::exit::ExitTransactionStatus) -> Self {
218 ExitTransactionStatus {
219 vtxo_id: v.vtxo_id,
220 state: v.state.into(),
221 history: v.history.map(|h| h.into_iter().map(ExitState::from).collect()),
222 transactions: v.transactions.into_iter().map(ExitTransactionPackage::from).collect(),
223 }
224 }
225}
226
227#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
229#[cfg_attr(feature = "utoipa", derive(ToSchema))]
230pub struct PendingBoardInfo {
231 pub funding_tx: TransactionInfo,
235 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
240 pub vtxos: Vec<VtxoId>,
241 #[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
243 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
244 pub amount: Amount,
245 pub movement_id: u32,
247}
248
249impl From<bark::persist::models::PendingBoard> for PendingBoardInfo {
250 fn from(v: bark::persist::models::PendingBoard) -> Self {
251 PendingBoardInfo {
252 funding_tx: v.funding_tx.into(),
253 vtxos: v.vtxos,
254 amount: v.amount,
255 movement_id: v.movement_id.0,
256 }
257 }
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
261#[serde(tag = "status", rename_all = "kebab-case")]
262#[cfg_attr(feature = "utoipa", derive(ToSchema))]
263pub enum RoundStatus {
264 SyncError {
266 error: String,
267 },
268 Confirmed {
270 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
271 funding_txid: Txid,
272 },
273 Unconfirmed {
275 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
276 funding_txid: Txid,
277 },
278 Pending,
280 Failed {
282 error: String,
283 },
284 Canceled,
286}
287
288impl RoundStatus {
289 pub fn is_final(&self) -> bool {
291 match self {
292 Self::SyncError { .. } => false,
293 Self::Confirmed { .. } => true,
294 Self::Unconfirmed { .. } => false,
295 Self::Pending { .. } => false,
296 Self::Failed { .. } => true,
297 Self::Canceled => true,
298 }
299 }
300
301 pub fn is_success(&self) -> bool {
303 match self {
304 Self::SyncError { .. } => false,
305 Self::Confirmed { .. } => true,
306 Self::Unconfirmed { .. } => true,
307 Self::Pending { .. } => false,
308 Self::Failed { .. } => false,
309 Self::Canceled => false,
310 }
311 }
312}
313
314impl From<bark::round::RoundStatus> for RoundStatus {
315 fn from(s: bark::round::RoundStatus) -> Self {
316 match s {
317 bark::round::RoundStatus::Confirmed { funding_txid } => {
318 Self::Confirmed { funding_txid }
319 },
320 bark::round::RoundStatus::Unconfirmed { funding_txid } => {
321 Self::Unconfirmed { funding_txid }
322 },
323 bark::round::RoundStatus::Pending => Self::Pending,
324 bark::round::RoundStatus::Failed { error } => Self::Failed { error },
325 bark::round::RoundStatus::Canceled => Self::Canceled,
326 }
327 }
328}
329
330#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
331#[cfg_attr(feature = "utoipa", derive(ToSchema))]
332pub struct RoundStateInfo {
333 pub round_state_id: u32,
334}
335
336#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
337#[cfg_attr(feature = "utoipa", derive(ToSchema))]
338pub struct InvoiceInfo {
339 pub invoice: String,
341}
342
343#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
344#[cfg_attr(feature = "utoipa", derive(ToSchema))]
345pub struct OffboardResult {
346 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
348 pub offboard_txid: Txid,
349}
350
351#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
352#[cfg_attr(feature = "utoipa", derive(ToSchema))]
353pub struct LightningReceiveInfo {
354 #[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
356 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
357 pub amount: Amount,
358 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
360 pub payment_hash: PaymentHash,
361 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
363 pub payment_preimage: Preimage,
364 pub preimage_revealed_at: Option<chrono::DateTime<chrono::Local>>,
366 pub finished_at: Option<chrono::DateTime<chrono::Local>>,
368 pub invoice: String,
370 #[serde(default, deserialize_with = "serde_utils::null_as_default")]
374 #[cfg_attr(feature = "utoipa", schema(required = true))]
375 pub htlc_vtxos: Vec<WalletVtxoInfo>,
376}
377
378impl From<bark::persist::models::LightningReceive> for LightningReceiveInfo {
379 fn from(v: bark::persist::models::LightningReceive) -> Self {
380 LightningReceiveInfo {
381 payment_hash: v.payment_hash,
382 payment_preimage: v.payment_preimage,
383 preimage_revealed_at: v.preimage_revealed_at,
384 invoice: v.invoice.to_string(),
385 htlc_vtxos: v.htlc_vtxos.into_iter()
386 .map(crate::primitives::WalletVtxoInfo::from).collect(),
387 amount: v.invoice.amount_milli_satoshis().map(Amount::from_msat_floor)
388 .unwrap_or(Amount::ZERO),
389 finished_at: v.finished_at,
390 }
391 }
392}
393
394#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
395#[cfg_attr(feature = "utoipa", derive(ToSchema))]
396pub struct LightningSendInfo {
397 #[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
399 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
400 pub amount: Amount,
401 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
403 pub payment_hash: PaymentHash,
404 pub invoice: String,
406 #[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
408 pub preimage: Option<Preimage>,
409 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<WalletVtxoInfo>))]
411 pub htlc_vtxos: Vec<WalletVtxoInfo>,
412 #[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
414 pub finished_at: Option<chrono::DateTime<chrono::Local>>,
415}
416
417impl From<bark::persist::models::LightningSend> for LightningSendInfo {
418 fn from(v: bark::persist::models::LightningSend) -> Self {
419 LightningSendInfo {
420 payment_hash: v.invoice.payment_hash(),
421 invoice: v.invoice.to_string(),
422 htlc_vtxos: v.htlc_vtxos.into_iter()
423 .map(crate::primitives::WalletVtxoInfo::from).collect(),
424 amount: v.amount,
425 preimage: v.preimage,
426 finished_at: v.finished_at,
427 }
428 }
429}
430
431#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
433#[serde(tag = "status", rename_all = "kebab-case")]
434#[cfg_attr(feature = "utoipa", derive(ToSchema))]
435pub enum LightningMovement {
436 Receive(LightningReceiveInfo),
438 Send(LightningSendInfo),
440}
441
442#[cfg(test)]
443mod test {
444 use bitcoin::FeeRate;
445 use super::*;
446
447 fn lightning_receive_base_json() -> serde_json::Value {
448 serde_json::json!({
449 "amount_sat": 1000,
450 "payment_hash": "0000000000000000000000000000000000000000000000000000000000000000",
451 "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000",
452 "preimage_revealed_at": null,
453 "finished_at": null,
454 "invoice": "lnbc1",
455 })
456 }
457
458 #[test]
459 fn deserialize_lightning_receive_htlc_vtxos_missing() {
460 let json = lightning_receive_base_json();
461 serde_json::from_value::<LightningReceiveInfo>(json).unwrap();
462 }
463
464 #[test]
465 fn deserialize_lightning_receive_htlc_vtxos_null() {
466 let mut json = lightning_receive_base_json();
467 json["htlc_vtxos"] = serde_json::json!(null);
468 serde_json::from_value::<LightningReceiveInfo>(json).unwrap();
469 }
470
471 #[test]
472 fn deserialize_lightning_receive_htlc_vtxos_empty() {
473 let mut json = lightning_receive_base_json();
474 json["htlc_vtxos"] = serde_json::json!([]);
475 serde_json::from_value::<LightningReceiveInfo>(json).unwrap();
476 }
477
478 #[test]
479 fn ark_info_fields() {
480 #[allow(unused, deprecated)]
484 fn convert(j: ArkInfo) -> ark::ArkInfo {
485 ark::ArkInfo {
486 network: j.network,
487 server_pubkey: j.server_pubkey,
488 mailbox_pubkey: j.mailbox_pubkey,
489 round_interval: j.round_interval,
490 nb_round_nonces: j.nb_round_nonces,
491 vtxo_exit_delta: j.vtxo_exit_delta,
492 vtxo_expiry_delta: j.vtxo_expiry_delta,
493 htlc_send_expiry_delta: j.htlc_send_expiry_delta,
494 htlc_expiry_delta: j.htlc_expiry_delta,
495 max_vtxo_amount: j.max_vtxo_amount,
496 required_board_confirmations: j.required_board_confirmations,
497 max_user_invoice_cltv_delta: j.max_user_invoice_cltv_delta,
498 min_board_amount: j.min_board_amount,
499 offboard_feerate: FeeRate::from_sat_per_kwu(j.offboard_feerate_sat_per_kvb / 4),
500 ln_receive_anti_dos_required: j.ln_receive_anti_dos_required,
501 fees: j.fees.into(),
502 max_vtxo_exit_depth: j.max_vtxo_exit_depth,
503 }
504 }
505 }
506}
507