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 fn from(v: T) -> Self {
86 let v = v.borrow();
87 ArkInfo {
88 network: v.network,
89 server_pubkey: v.server_pubkey,
90 mailbox_pubkey: v.mailbox_pubkey,
91 round_interval: v.round_interval,
92 nb_round_nonces: v.nb_round_nonces,
93 vtxo_exit_delta: v.vtxo_exit_delta,
94 vtxo_expiry_delta: v.vtxo_expiry_delta,
95 htlc_send_expiry_delta: v.htlc_send_expiry_delta,
96 htlc_expiry_delta: v.htlc_expiry_delta,
97 max_vtxo_amount: v.max_vtxo_amount,
98 required_board_confirmations: v.required_board_confirmations,
99 max_user_invoice_cltv_delta: v.max_user_invoice_cltv_delta,
100 min_board_amount: v.min_board_amount,
101 offboard_feerate_sat_per_kvb: v.offboard_feerate.to_sat_per_kwu() * 4,
102 ln_receive_anti_dos_required: v.ln_receive_anti_dos_required,
103 fees: v.fees.clone().into(),
104 max_vtxo_exit_depth: v.max_vtxo_exit_depth,
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, Serialize, Deserialize)]
260#[serde(tag = "status", rename_all = "kebab-case")]
261#[cfg_attr(feature = "utoipa", derive(ToSchema))]
262pub enum RoundStatus {
263 SyncError {
265 error: String,
266 },
267 Confirmed {
269 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
270 funding_txid: Txid,
271 },
272 Unconfirmed {
274 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
275 funding_txid: Txid,
276 },
277 Pending,
279 Failed {
281 error: String,
282 },
283 Canceled,
285}
286
287impl RoundStatus {
288 pub fn is_final(&self) -> bool {
290 match self {
291 Self::SyncError { .. } => false,
292 Self::Confirmed { .. } => true,
293 Self::Unconfirmed { .. } => false,
294 Self::Pending { .. } => false,
295 Self::Failed { .. } => true,
296 Self::Canceled => true,
297 }
298 }
299
300 pub fn is_success(&self) -> bool {
302 match self {
303 Self::SyncError { .. } => false,
304 Self::Confirmed { .. } => true,
305 Self::Unconfirmed { .. } => true,
306 Self::Pending { .. } => false,
307 Self::Failed { .. } => false,
308 Self::Canceled => false,
309 }
310 }
311}
312
313impl From<bark::round::RoundStatus> for RoundStatus {
314 fn from(s: bark::round::RoundStatus) -> Self {
315 match s {
316 bark::round::RoundStatus::Confirmed { funding_txid } => {
317 Self::Confirmed { funding_txid }
318 },
319 bark::round::RoundStatus::Unconfirmed { funding_txid } => {
320 Self::Unconfirmed { funding_txid }
321 },
322 bark::round::RoundStatus::Pending => Self::Pending,
323 bark::round::RoundStatus::Failed { error } => Self::Failed { error },
324 bark::round::RoundStatus::Canceled => Self::Canceled,
325 }
326 }
327}
328
329#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
330#[cfg_attr(feature = "utoipa", derive(ToSchema))]
331pub struct RoundStateInfo {
332 pub round_state_id: u32,
333}
334
335#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
336#[cfg_attr(feature = "utoipa", derive(ToSchema))]
337pub struct InvoiceInfo {
338 pub invoice: String,
340}
341
342#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
343#[cfg_attr(feature = "utoipa", derive(ToSchema))]
344pub struct OffboardResult {
345 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
347 pub offboard_txid: Txid,
348}
349
350#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
351#[cfg_attr(feature = "utoipa", derive(ToSchema))]
352pub struct LightningReceiveInfo {
353 #[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
355 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
356 pub amount: Amount,
357 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
359 pub payment_hash: PaymentHash,
360 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
362 pub payment_preimage: Preimage,
363 pub preimage_revealed_at: Option<chrono::DateTime<chrono::Local>>,
365 pub finished_at: Option<chrono::DateTime<chrono::Local>>,
367 pub invoice: String,
369 #[serde(default, deserialize_with = "serde_utils::null_as_default")]
373 #[cfg_attr(feature = "utoipa", schema(required = true))]
374 pub htlc_vtxos: Vec<WalletVtxoInfo>,
375}
376
377impl From<bark::persist::models::LightningReceive> for LightningReceiveInfo {
378 fn from(v: bark::persist::models::LightningReceive) -> Self {
379 LightningReceiveInfo {
380 payment_hash: v.payment_hash,
381 payment_preimage: v.payment_preimage,
382 preimage_revealed_at: v.preimage_revealed_at,
383 invoice: v.invoice.to_string(),
384 htlc_vtxos: v.htlc_vtxos.into_iter()
385 .map(crate::primitives::WalletVtxoInfo::from).collect(),
386 amount: v.invoice.amount_milli_satoshis().map(Amount::from_msat_floor)
387 .unwrap_or(Amount::ZERO),
388 finished_at: v.finished_at,
389 }
390 }
391}
392
393#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
394#[cfg_attr(feature = "utoipa", derive(ToSchema))]
395pub struct LightningSendInfo {
396 #[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
398 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
399 pub amount: Amount,
400 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
402 pub payment_hash: PaymentHash,
403 pub invoice: String,
405 #[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
407 pub preimage: Option<Preimage>,
408 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<WalletVtxoInfo>))]
410 pub htlc_vtxos: Vec<WalletVtxoInfo>,
411 #[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
413 pub finished_at: Option<chrono::DateTime<chrono::Local>>,
414}
415
416impl From<bark::persist::models::LightningSend> for LightningSendInfo {
417 fn from(v: bark::persist::models::LightningSend) -> Self {
418 LightningSendInfo {
419 payment_hash: v.invoice.payment_hash(),
420 invoice: v.invoice.to_string(),
421 htlc_vtxos: v.htlc_vtxos.into_iter()
422 .map(crate::primitives::WalletVtxoInfo::from).collect(),
423 amount: v.amount,
424 preimage: v.preimage,
425 finished_at: v.finished_at,
426 }
427 }
428}
429
430#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
432#[serde(tag = "status", rename_all = "kebab-case")]
433#[cfg_attr(feature = "utoipa", derive(ToSchema))]
434pub enum LightningMovement {
435 Receive(LightningReceiveInfo),
437 Send(LightningSendInfo),
439}
440
441#[cfg(test)]
442mod test {
443 use bitcoin::FeeRate;
444 use super::*;
445
446 fn lightning_receive_base_json() -> serde_json::Value {
447 serde_json::json!({
448 "amount_sat": 1000,
449 "payment_hash": "0000000000000000000000000000000000000000000000000000000000000000",
450 "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000",
451 "preimage_revealed_at": null,
452 "finished_at": null,
453 "invoice": "lnbc1",
454 })
455 }
456
457 #[test]
458 fn deserialize_lightning_receive_htlc_vtxos_missing() {
459 let json = lightning_receive_base_json();
460 serde_json::from_value::<LightningReceiveInfo>(json).unwrap();
461 }
462
463 #[test]
464 fn deserialize_lightning_receive_htlc_vtxos_null() {
465 let mut json = lightning_receive_base_json();
466 json["htlc_vtxos"] = serde_json::json!(null);
467 serde_json::from_value::<LightningReceiveInfo>(json).unwrap();
468 }
469
470 #[test]
471 fn deserialize_lightning_receive_htlc_vtxos_empty() {
472 let mut json = lightning_receive_base_json();
473 json["htlc_vtxos"] = serde_json::json!([]);
474 serde_json::from_value::<LightningReceiveInfo>(json).unwrap();
475 }
476
477 #[test]
478 fn ark_info_fields() {
479 #[allow(unused)]
483 fn convert(j: ArkInfo) -> ark::ArkInfo {
484 ark::ArkInfo {
485 network: j.network,
486 server_pubkey: j.server_pubkey,
487 mailbox_pubkey: j.mailbox_pubkey,
488 round_interval: j.round_interval,
489 nb_round_nonces: j.nb_round_nonces,
490 vtxo_exit_delta: j.vtxo_exit_delta,
491 vtxo_expiry_delta: j.vtxo_expiry_delta,
492 htlc_send_expiry_delta: j.htlc_send_expiry_delta,
493 htlc_expiry_delta: j.htlc_expiry_delta,
494 max_vtxo_amount: j.max_vtxo_amount,
495 required_board_confirmations: j.required_board_confirmations,
496 max_user_invoice_cltv_delta: j.max_user_invoice_cltv_delta,
497 min_board_amount: j.min_board_amount,
498 offboard_feerate: FeeRate::from_sat_per_kwu(j.offboard_feerate_sat_per_kvb / 4),
499 ln_receive_anti_dos_required: j.ln_receive_anti_dos_required,
500 fees: j.fees.into(),
501 max_vtxo_exit_depth: j.max_vtxo_exit_depth,
502 }
503 }
504 }
505}
506