1#[cfg(feature = "onchain_bdk")]
2pub mod onchain;
3
4use std::borrow::Borrow;
5use std::time::Duration;
6
7use bitcoin::secp256k1::PublicKey;
8use bitcoin::{Amount, FeeRate, Txid, SignedAmount};
9use chrono::DateTime;
10#[cfg(feature = "utoipa")]
11use utoipa::ToSchema;
12
13use ark::lightning::{PaymentHash, Preimage};
14use ark::VtxoId;
15use bark::movement::{MovementId, MovementStatus};
16use bitcoin_ext::{BlockDelta, BlockHeight};
17
18use crate::exit::error::ExitError;
19use crate::exit::package::ExitTransactionPackage;
20use crate::exit::ExitState;
21use crate::primitives::{VtxoInfo, 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 #[serde(with = "serde_utils::duration")]
35 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
36 pub round_interval: Duration,
37 pub nb_round_nonces: usize,
39 pub vtxo_exit_delta: BlockDelta,
41 pub vtxo_expiry_delta: BlockDelta,
43 pub htlc_send_expiry_delta: BlockDelta,
45 pub htlc_expiry_delta: BlockDelta,
47 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
49 pub max_vtxo_amount: Option<Amount>,
50 pub max_arkoor_depth: u16,
52 pub required_board_confirmations: usize,
54 pub max_user_invoice_cltv_delta: u16,
57 #[serde(rename = "min_board_amount_sat", with = "bitcoin::amount::serde::as_sat")]
59 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
60 pub min_board_amount: Amount,
61 pub offboard_feerate_sat_per_kvb: u64,
63 pub ln_receive_anti_dos_required: bool,
67}
68
69impl<T: Borrow<ark::ArkInfo>> From<T> for ArkInfo {
70 fn from(v: T) -> Self {
71 let v = v.borrow();
72 ArkInfo {
73 network: v.network,
74 server_pubkey: v.server_pubkey,
75 round_interval: v.round_interval,
76 nb_round_nonces: v.nb_round_nonces,
77 vtxo_exit_delta: v.vtxo_exit_delta,
78 vtxo_expiry_delta: v.vtxo_expiry_delta,
79 htlc_send_expiry_delta: v.htlc_send_expiry_delta,
80 htlc_expiry_delta: v.htlc_expiry_delta,
81 max_vtxo_amount: v.max_vtxo_amount,
82 max_arkoor_depth: v.max_arkoor_depth,
83 required_board_confirmations: v.required_board_confirmations,
84 max_user_invoice_cltv_delta: v.max_user_invoice_cltv_delta,
85 min_board_amount: v.min_board_amount,
86 offboard_feerate_sat_per_kvb: v.offboard_feerate.to_sat_per_kwu() * 4,
87 ln_receive_anti_dos_required: v.ln_receive_anti_dos_required,
88 }
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
93#[cfg_attr(feature = "utoipa", derive(ToSchema))]
94pub struct LightningReceiveBalance {
95 #[serde(rename = "total_sat", with = "bitcoin::amount::serde::as_sat")]
96 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
97 pub total: Amount,
98 #[serde(rename = "claimable_sat", with = "bitcoin::amount::serde::as_sat")]
99 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
100 pub claimable: Amount,
101}
102
103impl From<bark::LightningReceiveBalance> for LightningReceiveBalance {
104 fn from(v: bark::LightningReceiveBalance) -> Self {
105 LightningReceiveBalance {
106 total: v.total,
107 claimable: v.claimable,
108 }
109 }
110}
111
112#[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")]
116 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
117 pub spendable: Amount,
118 #[serde(rename = "pending_lightning_send_sat", with = "bitcoin::amount::serde::as_sat")]
119 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
120 pub pending_lightning_send: Amount,
121 pub pending_lightning_receive: LightningReceiveBalance,
122 #[serde(rename = "pending_in_round_sat", with = "bitcoin::amount::serde::as_sat")]
123 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
124 pub pending_in_round: Amount,
125 #[serde(rename = "pending_board_sat", with = "bitcoin::amount::serde::as_sat")]
126 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
127 pub pending_board: Amount,
128 #[serde(
129 default,
130 rename = "pending_exit_sat",
131 with = "bitcoin::amount::serde::as_sat::opt",
132 skip_serializing_if = "Option::is_none",
133 )]
134 #[cfg_attr(feature = "utoipa", schema(value_type = u64, nullable=true))]
135 pub pending_exit: Option<Amount>,
136}
137
138impl From<bark::Balance> for Balance {
139 fn from(v: bark::Balance) -> Self {
140 Balance {
141 spendable: v.spendable,
142 pending_in_round: v.pending_in_round,
143 pending_lightning_send: v.pending_lightning_send,
144 pending_lightning_receive: v.pending_lightning_receive.into(),
145 pending_exit: v.pending_exit,
146 pending_board: v.pending_board,
147 }
148 }
149}
150
151#[derive(Debug, Clone, Deserialize, Serialize)]
152#[cfg_attr(feature = "utoipa", derive(ToSchema))]
153pub struct Config {
154 pub ark: String,
156 pub bitcoind: Option<String>,
158 pub bitcoind_cookie: Option<String>,
160 pub bitcoind_user: Option<String>,
162 pub bitcoind_pass: Option<String>,
164 pub esplora: Option<String>,
166 pub vtxo_refresh_expiry_threshold: BlockHeight,
168 #[serde(rename = "fallback_fee_rate_kvb", with = "serde_utils::fee_rate_sats_per_kvb")]
169 #[cfg_attr(feature = "utoipa", schema(value_type = u64, nullable = true))]
170 pub fallback_fee_rate: Option<FeeRate>,
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
174#[cfg_attr(feature = "utoipa", derive(ToSchema))]
175pub struct ExitProgressResponse {
176 pub exits: Vec<ExitProgressStatus>,
178 pub done: bool,
180 pub claimable_height: Option<u32>,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
185#[cfg_attr(feature = "utoipa", derive(ToSchema))]
186pub struct ExitProgressStatus {
187 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
189 pub vtxo_id: VtxoId,
190 pub state: ExitState,
192 #[serde(default, skip_serializing_if = "Option::is_none")]
194 pub error: Option<ExitError>,
195}
196
197impl From<bark::exit::models::ExitProgressStatus> for ExitProgressStatus {
198 fn from(v: bark::exit::models::ExitProgressStatus) -> Self {
199 ExitProgressStatus {
200 vtxo_id: v.vtxo_id,
201 state: v.state.into(),
202 error: v.error.map(ExitError::from),
203 }
204 }
205}
206
207#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
208#[cfg_attr(feature = "utoipa", derive(ToSchema))]
209pub struct ExitTransactionStatus {
210 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
212 pub vtxo_id: VtxoId,
213 pub state: ExitState,
215 #[serde(default, skip_serializing_if = "Option::is_none")]
217 pub history: Option<Vec<ExitState>>,
218 #[serde(default, skip_serializing_if = "Vec::is_empty")]
220 pub transactions: Vec<ExitTransactionPackage>,
221}
222
223impl From<bark::exit::models::ExitTransactionStatus> for ExitTransactionStatus {
224 fn from(v: bark::exit::models::ExitTransactionStatus) -> Self {
225 ExitTransactionStatus {
226 vtxo_id: v.vtxo_id,
227 state: v.state.into(),
228 history: v.history.map(|h| h.into_iter().map(ExitState::from).collect()),
229 transactions: v.transactions.into_iter().map(ExitTransactionPackage::from).collect(),
230 }
231 }
232}
233
234#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
236#[cfg_attr(feature = "utoipa", derive(ToSchema))]
237pub struct Board {
238 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
242 pub funding_txid: Txid,
243 pub vtxos: Vec<VtxoInfo>,
248}
249
250impl From<bark::Board> for Board {
251 fn from(v: bark::Board) -> Self {
252 Board {
253 funding_txid: v.funding_txid,
254 vtxos: v.vtxos.into_iter().map(VtxoInfo::from).collect(),
255 }
256 }
257}
258
259#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
261#[cfg_attr(feature = "utoipa", derive(ToSchema))]
262pub struct Movement {
263 #[cfg_attr(feature = "utoipa", schema(value_type = u32))]
265 pub id: MovementId,
266 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
268 pub status: MovementStatus,
269 pub subsystem: MovementSubsystem,
272 #[serde(default, skip_serializing_if = "Option::is_none")]
275 pub metadata: Option<String>,
276 #[serde(rename="intended_balance_sat", with="bitcoin::amount::serde::as_sat")]
279 #[cfg_attr(feature = "utoipa", schema(value_type = i64))]
280 pub intended_balance: SignedAmount,
281 #[serde(rename="effective_balance_sat", with="bitcoin::amount::serde::as_sat")]
285 #[cfg_attr(feature = "utoipa", schema(value_type = i64))]
286 pub effective_balance: SignedAmount,
287 #[serde(rename="offchain_fee_sat", with="bitcoin::amount::serde::as_sat")]
291 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
292 pub offchain_fee: Amount,
293 pub sent_to: Vec<MovementDestination>,
295 pub received_on: Vec<MovementDestination>,
298 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
301 pub input_vtxos: Vec<VtxoId>,
302 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
305 pub output_vtxos: Vec<VtxoId>,
306 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
311 pub exited_vtxos: Vec<VtxoId>,
312 pub time: MovementTimestamp,
314}
315
316impl TryFrom<bark::movement::Movement> for Movement {
317 type Error = serde_json::Error;
318
319 fn try_from(m: bark::movement::Movement) -> Result<Self, Self::Error> {
320 Ok(Movement {
321 id: m.id,
322 status: m.status,
323 subsystem: MovementSubsystem::from(m.subsystem),
324 metadata: if m.metadata.is_empty() { None } else {
325 Some(serde_json::to_string(&m.metadata)?)
326 },
327 intended_balance: m.intended_balance,
328 effective_balance: m.effective_balance,
329 offchain_fee: m.offchain_fee,
330 sent_to: m.sent_to.into_iter().map(MovementDestination::from).collect(),
331 received_on: m.received_on.into_iter().map(MovementDestination::from).collect(),
332 input_vtxos: m.input_vtxos,
333 output_vtxos: m.output_vtxos,
334 exited_vtxos: m.exited_vtxos,
335 time: MovementTimestamp::from(m.time),
336 })
337 }
338}
339
340#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
343#[cfg_attr(feature = "utoipa", derive(ToSchema))]
344pub struct MovementDestination {
345 pub destination: String,
347 #[serde(rename="amount_sat", with="bitcoin::amount::serde::as_sat")]
349 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
350 pub amount: Amount,
351}
352
353impl From<bark::movement::MovementDestination> for MovementDestination {
354 fn from(d: bark::movement::MovementDestination) -> Self {
355 MovementDestination {
356 destination: d.destination,
357 amount: d.amount,
358 }
359 }
360}
361
362#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
365#[cfg_attr(feature = "utoipa", derive(ToSchema))]
366pub struct MovementSubsystem {
367 pub name: String,
369 pub kind: String,
371}
372
373impl From<bark::movement::MovementSubsystem> for MovementSubsystem {
374 fn from(s: bark::movement::MovementSubsystem) -> Self {
375 MovementSubsystem {
376 name: s.name,
377 kind: s.kind,
378 }
379 }
380}
381
382#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
384#[cfg_attr(feature = "utoipa", derive(ToSchema))]
385pub struct MovementTimestamp {
386 pub created_at: DateTime<chrono::Utc>,
388 pub updated_at: DateTime<chrono::Utc>,
390 #[serde(default, skip_serializing_if = "Option::is_none")]
392 pub completed_at: Option<DateTime<chrono::Utc>>,
393}
394
395impl From<bark::movement::MovementTimestamp> for MovementTimestamp {
396 fn from(t: bark::movement::MovementTimestamp) -> Self {
397 MovementTimestamp {
398 created_at: t.created_at,
399 updated_at: t.updated_at,
400 completed_at: t.completed_at,
401 }
402 }
403}
404
405#[derive(Debug, Clone, Serialize, Deserialize)]
406#[serde(tag = "result", rename_all = "lowercase")]
407#[cfg_attr(feature = "utoipa", derive(ToSchema))]
408pub enum RoundStatus {
409 Confirmed {
411 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
412 funding_txid: Txid,
413 },
414 Unconfirmed {
416 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
417 funding_txid: Txid,
418 },
419 Pending {
421 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
422 unsigned_funding_txids: Vec<Txid>,
423 },
424 Failed {
426 error: String,
427 },
428}
429
430impl RoundStatus {
431 pub fn is_final(&self) -> bool {
433 match self {
434 Self::Confirmed { .. } => true,
435 Self::Unconfirmed { .. } => false,
436 Self::Pending { .. } => false,
437 Self::Failed { .. } => true,
438 }
439 }
440
441 pub fn is_success(&self) -> bool {
443 match self {
444 Self::Confirmed { .. } => true,
445 Self::Unconfirmed { .. } => true,
446 Self::Pending { .. } => false,
447 Self::Failed { .. } => false,
448 }
449 }
450}
451
452impl From<bark::round::RoundStatus> for RoundStatus {
453 fn from(s: bark::round::RoundStatus) -> Self {
454 match s {
455 bark::round::RoundStatus::Confirmed { funding_txid } => Self::Confirmed { funding_txid },
456 bark::round::RoundStatus::Unconfirmed { funding_txid } => Self::Unconfirmed { funding_txid },
457 bark::round::RoundStatus::Pending { unsigned_funding_txids } => Self::Pending { unsigned_funding_txids },
458 bark::round::RoundStatus::Failed { error } => Self::Failed { error },
459 }
460 }
461}
462
463#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
464#[cfg_attr(feature = "utoipa", derive(ToSchema))]
465pub struct InvoiceInfo {
466 pub invoice: String,
468}
469
470#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
471#[cfg_attr(feature = "utoipa", derive(ToSchema))]
472pub struct LightningReceiveInfo {
473 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
475 pub payment_hash: PaymentHash,
476 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
478 pub payment_preimage: Preimage,
479 pub preimage_revealed_at: Option<chrono::DateTime<chrono::Utc>>,
481 pub invoice: String,
483 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<WalletVtxoInfo>, nullable = true))]
487 pub htlc_vtxos: Option<Vec<WalletVtxoInfo>>,
488}
489
490impl From<bark::persist::models::LightningReceive> for LightningReceiveInfo {
491 fn from(v: bark::persist::models::LightningReceive) -> Self {
492 LightningReceiveInfo {
493 payment_hash: v.payment_hash,
494 payment_preimage: v.payment_preimage,
495 preimage_revealed_at: v.preimage_revealed_at.map(|ts| {
496 chrono::DateTime::from_timestamp_secs(ts as i64)
497 .expect("timestamp is valid")
498 }),
499 invoice: v.invoice.to_string(),
500 htlc_vtxos: v.htlc_vtxos.map(|vtxos| vtxos.into_iter()
501 .map(crate::primitives::WalletVtxoInfo::from).collect()),
502 }
503 }
504}
505
506#[cfg(test)]
507mod test {
508 use super::*;
509
510 #[test]
511 fn ark_info_fields() {
512 #[allow(unused)]
516 fn convert(j: ArkInfo) -> ark::ArkInfo {
517 ark::ArkInfo {
518 network: j.network,
519 server_pubkey: j.server_pubkey,
520 round_interval: j.round_interval,
521 nb_round_nonces: j.nb_round_nonces,
522 vtxo_exit_delta: j.vtxo_exit_delta,
523 vtxo_expiry_delta: j.vtxo_expiry_delta,
524 htlc_send_expiry_delta: j.htlc_send_expiry_delta,
525 htlc_expiry_delta: j.htlc_expiry_delta,
526 max_vtxo_amount: j.max_vtxo_amount,
527 max_arkoor_depth: j.max_arkoor_depth,
528 required_board_confirmations: j.required_board_confirmations,
529 max_user_invoice_cltv_delta: j.max_user_invoice_cltv_delta,
530 min_board_amount: j.min_board_amount,
531 offboard_feerate: FeeRate::from_sat_per_kwu(j.offboard_feerate_sat_per_kvb / 4),
532 ln_receive_anti_dos_required: j.ln_receive_anti_dos_required,
533 }
534 }
535 }
536}
537