1
2use std::borrow::Borrow;
3use std::time::Duration;
4
5use bitcoin::secp256k1::PublicKey;
6use bitcoin::{Amount, FeeRate, Txid, SignedAmount};
7use chrono::DateTime;
8#[cfg(feature = "utoipa")]
9use utoipa::ToSchema;
10
11use ark::lightning::{PaymentHash, Preimage};
12use ark::VtxoId;
13use bark::movement::{MovementId, MovementStatus};
14use bitcoin_ext::{BlockDelta, BlockHeight};
15
16use crate::exit::error::ExitError;
17use crate::exit::package::ExitTransactionPackage;
18use crate::exit::ExitState;
19use crate::primitives::{VtxoInfo, WalletVtxoInfo};
20use crate::serde_utils;
21
22#[derive(Debug, Clone, Deserialize, Serialize)]
23#[cfg_attr(feature = "utoipa", derive(ToSchema))]
24pub struct ArkInfo {
25 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
27 pub network: bitcoin::Network,
28 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
30 pub server_pubkey: PublicKey,
31 #[serde(with = "serde_utils::duration")]
33 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
34 pub round_interval: Duration,
35 pub nb_round_nonces: usize,
37 pub vtxo_exit_delta: BlockDelta,
39 pub vtxo_expiry_delta: BlockDelta,
41 pub htlc_send_expiry_delta: BlockDelta,
43 pub htlc_expiry_delta: BlockDelta,
45 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
47 pub max_vtxo_amount: Option<Amount>,
48 pub max_arkoor_depth: u16,
50 pub required_board_confirmations: usize,
52 pub max_user_invoice_cltv_delta: u16,
55 #[serde(rename = "min_board_amount_sat", with = "bitcoin::amount::serde::as_sat")]
57 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
58 pub min_board_amount: Amount,
59 pub offboard_feerate_sat_per_kvb: u64,
61 pub ln_receive_anti_dos_required: bool,
65}
66
67impl<T: Borrow<ark::ArkInfo>> From<T> for ArkInfo {
68 fn from(v: T) -> Self {
69 let v = v.borrow();
70 ArkInfo {
71 network: v.network,
72 server_pubkey: v.server_pubkey,
73 round_interval: v.round_interval,
74 nb_round_nonces: v.nb_round_nonces,
75 vtxo_exit_delta: v.vtxo_exit_delta,
76 vtxo_expiry_delta: v.vtxo_expiry_delta,
77 htlc_send_expiry_delta: v.htlc_send_expiry_delta,
78 htlc_expiry_delta: v.htlc_expiry_delta,
79 max_vtxo_amount: v.max_vtxo_amount,
80 max_arkoor_depth: v.max_arkoor_depth,
81 required_board_confirmations: v.required_board_confirmations,
82 max_user_invoice_cltv_delta: v.max_user_invoice_cltv_delta,
83 min_board_amount: v.min_board_amount,
84 offboard_feerate_sat_per_kvb: v.offboard_feerate.to_sat_per_kwu() * 4,
85 ln_receive_anti_dos_required: v.ln_receive_anti_dos_required,
86 }
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
91#[cfg_attr(feature = "utoipa", derive(ToSchema))]
92pub struct LightningReceiveBalance {
93 #[serde(rename = "total_sat", with = "bitcoin::amount::serde::as_sat")]
94 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
95 pub total: Amount,
96 #[serde(rename = "claimable_sat", with = "bitcoin::amount::serde::as_sat")]
97 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
98 pub claimable: Amount,
99}
100
101impl From<bark::LightningReceiveBalance> for LightningReceiveBalance {
102 fn from(v: bark::LightningReceiveBalance) -> Self {
103 LightningReceiveBalance {
104 total: v.total,
105 claimable: v.claimable,
106 }
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
111#[cfg_attr(feature = "utoipa", derive(ToSchema))]
112pub struct Balance {
113 #[serde(rename = "spendable_sat", with = "bitcoin::amount::serde::as_sat")]
114 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
115 pub spendable: Amount,
116 #[serde(rename = "pending_lightning_send_sat", with = "bitcoin::amount::serde::as_sat")]
117 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
118 pub pending_lightning_send: Amount,
119 pub pending_lightning_receive: LightningReceiveBalance,
120 #[serde(rename = "pending_in_round_sat", with = "bitcoin::amount::serde::as_sat")]
121 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
122 pub pending_in_round: Amount,
123 #[serde(rename = "pending_board_sat", with = "bitcoin::amount::serde::as_sat")]
124 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
125 pub pending_board: Amount,
126 #[serde(
127 default,
128 rename = "pending_exit_sat",
129 with = "bitcoin::amount::serde::as_sat::opt",
130 skip_serializing_if = "Option::is_none",
131 )]
132 #[cfg_attr(feature = "utoipa", schema(value_type = u64, nullable=true))]
133 pub pending_exit: Option<Amount>,
134}
135
136impl From<bark::Balance> for Balance {
137 fn from(v: bark::Balance) -> Self {
138 Balance {
139 spendable: v.spendable,
140 pending_in_round: v.pending_in_round,
141 pending_lightning_send: v.pending_lightning_send,
142 pending_lightning_receive: v.pending_lightning_receive.into(),
143 pending_exit: v.pending_exit,
144 pending_board: v.pending_board,
145 }
146 }
147}
148
149#[derive(Debug, Clone, Deserialize, Serialize)]
150#[cfg_attr(feature = "utoipa", derive(ToSchema))]
151pub struct Config {
152 pub ark: String,
154 pub bitcoind: Option<String>,
156 pub bitcoind_cookie: Option<String>,
158 pub bitcoind_user: Option<String>,
160 pub bitcoind_pass: Option<String>,
162 pub esplora: Option<String>,
164 pub vtxo_refresh_expiry_threshold: BlockHeight,
166 #[serde(rename = "fallback_fee_rate_kvb", with = "serde_utils::fee_rate_sats_per_kvb")]
167 #[cfg_attr(feature = "utoipa", schema(value_type = u64, nullable = true))]
168 pub fallback_fee_rate: Option<FeeRate>,
169}
170
171#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
172#[cfg_attr(feature = "utoipa", derive(ToSchema))]
173pub struct ExitProgressResponse {
174 pub exits: Vec<ExitProgressStatus>,
176 pub done: bool,
178 pub claimable_height: Option<u32>,
180}
181
182#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
183#[cfg_attr(feature = "utoipa", derive(ToSchema))]
184pub struct ExitProgressStatus {
185 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
187 pub vtxo_id: VtxoId,
188 pub state: ExitState,
190 #[serde(default, skip_serializing_if = "Option::is_none")]
192 pub error: Option<ExitError>,
193}
194
195impl From<bark::exit::models::ExitProgressStatus> for ExitProgressStatus {
196 fn from(v: bark::exit::models::ExitProgressStatus) -> Self {
197 ExitProgressStatus {
198 vtxo_id: v.vtxo_id,
199 state: v.state.into(),
200 error: v.error.map(ExitError::from),
201 }
202 }
203}
204
205#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
206#[cfg_attr(feature = "utoipa", derive(ToSchema))]
207pub struct ExitTransactionStatus {
208 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
210 pub vtxo_id: VtxoId,
211 pub state: ExitState,
213 #[serde(default, skip_serializing_if = "Option::is_none")]
215 pub history: Option<Vec<ExitState>>,
216 #[serde(default, skip_serializing_if = "Vec::is_empty")]
218 pub transactions: Vec<ExitTransactionPackage>,
219}
220
221impl From<bark::exit::models::ExitTransactionStatus> for ExitTransactionStatus {
222 fn from(v: bark::exit::models::ExitTransactionStatus) -> Self {
223 ExitTransactionStatus {
224 vtxo_id: v.vtxo_id,
225 state: v.state.into(),
226 history: v.history.map(|h| h.into_iter().map(ExitState::from).collect()),
227 transactions: v.transactions.into_iter().map(ExitTransactionPackage::from).collect(),
228 }
229 }
230}
231
232#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
234#[cfg_attr(feature = "utoipa", derive(ToSchema))]
235pub struct Board {
236 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
240 pub funding_txid: Txid,
241 pub vtxos: Vec<VtxoInfo>,
246}
247
248impl From<bark::Board> for Board {
249 fn from(v: bark::Board) -> Self {
250 Board {
251 funding_txid: v.funding_txid,
252 vtxos: v.vtxos.into_iter().map(VtxoInfo::from).collect(),
253 }
254 }
255}
256
257#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
259#[cfg_attr(feature = "utoipa", derive(ToSchema))]
260pub struct Movement {
261 #[cfg_attr(feature = "utoipa", schema(value_type = u32))]
263 pub id: MovementId,
264 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
266 pub status: MovementStatus,
267 pub subsystem: MovementSubsystem,
270 #[serde(default, skip_serializing_if = "Option::is_none")]
273 pub metadata: Option<String>,
274 #[serde(rename="intended_balance_sat", with="bitcoin::amount::serde::as_sat")]
277 #[cfg_attr(feature = "utoipa", schema(value_type = i64))]
278 pub intended_balance: SignedAmount,
279 #[serde(rename="effective_balance_sat", with="bitcoin::amount::serde::as_sat")]
283 #[cfg_attr(feature = "utoipa", schema(value_type = i64))]
284 pub effective_balance: SignedAmount,
285 #[serde(rename="offchain_fee_sat", with="bitcoin::amount::serde::as_sat")]
289 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
290 pub offchain_fee: Amount,
291 pub sent_to: Vec<MovementDestination>,
293 pub received_on: Vec<MovementDestination>,
296 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
299 pub input_vtxos: Vec<VtxoId>,
300 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
303 pub output_vtxos: Vec<VtxoId>,
304 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
309 pub exited_vtxos: Vec<VtxoId>,
310 pub time: MovementTimestamp,
312}
313
314impl TryFrom<bark::movement::Movement> for Movement {
315 type Error = serde_json::Error;
316
317 fn try_from(m: bark::movement::Movement) -> Result<Self, Self::Error> {
318 Ok(Movement {
319 id: m.id,
320 status: m.status,
321 subsystem: MovementSubsystem::from(m.subsystem),
322 metadata: if m.metadata.is_empty() { None } else {
323 Some(serde_json::to_string(&m.metadata)?)
324 },
325 intended_balance: m.intended_balance,
326 effective_balance: m.effective_balance,
327 offchain_fee: m.offchain_fee,
328 sent_to: m.sent_to.into_iter().map(MovementDestination::from).collect(),
329 received_on: m.received_on.into_iter().map(MovementDestination::from).collect(),
330 input_vtxos: m.input_vtxos,
331 output_vtxos: m.output_vtxos,
332 exited_vtxos: m.exited_vtxos,
333 time: MovementTimestamp::from(m.time),
334 })
335 }
336}
337
338#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
341#[cfg_attr(feature = "utoipa", derive(ToSchema))]
342pub struct MovementDestination {
343 pub destination: String,
345 #[serde(rename="amount_sat", with="bitcoin::amount::serde::as_sat")]
347 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
348 pub amount: Amount,
349}
350
351impl From<bark::movement::MovementDestination> for MovementDestination {
352 fn from(d: bark::movement::MovementDestination) -> Self {
353 MovementDestination {
354 destination: d.destination,
355 amount: d.amount,
356 }
357 }
358}
359
360#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
363#[cfg_attr(feature = "utoipa", derive(ToSchema))]
364pub struct MovementSubsystem {
365 pub name: String,
367 pub kind: String,
369}
370
371impl From<bark::movement::MovementSubsystem> for MovementSubsystem {
372 fn from(s: bark::movement::MovementSubsystem) -> Self {
373 MovementSubsystem {
374 name: s.name,
375 kind: s.kind,
376 }
377 }
378}
379
380#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
382#[cfg_attr(feature = "utoipa", derive(ToSchema))]
383pub struct MovementTimestamp {
384 pub created_at: DateTime<chrono::Utc>,
386 pub updated_at: DateTime<chrono::Utc>,
388 #[serde(default, skip_serializing_if = "Option::is_none")]
390 pub completed_at: Option<DateTime<chrono::Utc>>,
391}
392
393impl From<bark::movement::MovementTimestamp> for MovementTimestamp {
394 fn from(t: bark::movement::MovementTimestamp) -> Self {
395 MovementTimestamp {
396 created_at: t.created_at,
397 updated_at: t.updated_at,
398 completed_at: t.completed_at,
399 }
400 }
401}
402
403pub mod onchain {
404 use super::*;
405
406 #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
407 #[cfg_attr(feature = "utoipa", derive(ToSchema))]
408 pub struct Send {
409 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
410 pub txid: Txid,
411 }
412
413 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
414 #[cfg_attr(feature = "utoipa", derive(ToSchema))]
415 pub struct Address {
416 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
417 pub address: bitcoin::Address<bitcoin::address::NetworkUnchecked>,
418 }
419
420 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
421 #[cfg_attr(feature = "utoipa", derive(ToSchema))]
422 pub struct OnchainBalance {
423 #[serde(rename="total_sat", with="bitcoin::amount::serde::as_sat")]
425 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
426 pub total: Amount,
427 #[serde(rename="trusted_spendable_sat", with="bitcoin::amount::serde::as_sat")]
432 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
433 pub trusted_spendable: Amount,
434 #[serde(rename="immature_sat", with="bitcoin::amount::serde::as_sat")]
436 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
437 pub immature: Amount,
438 #[serde(rename="trusted_pending_sat", with="bitcoin::amount::serde::as_sat")]
440 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
441 pub trusted_pending: Amount,
442 #[serde(rename="untrusted_pending_sat", with="bitcoin::amount::serde::as_sat")]
444 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
445 pub untrusted_pending: Amount,
446 #[serde(rename="confirmed_sat", with="bitcoin::amount::serde::as_sat")]
448 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
449 pub confirmed: Amount,
450 }
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
454#[serde(tag = "result", rename_all = "lowercase")]
455#[cfg_attr(feature = "utoipa", derive(ToSchema))]
456pub enum RoundStatus {
457 Confirmed {
459 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
460 funding_txid: Txid,
461 },
462 Unconfirmed {
464 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
465 funding_txid: Txid,
466 },
467 Pending {
469 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
470 unsigned_funding_txids: Vec<Txid>,
471 },
472 Failed {
474 error: String,
475 },
476}
477
478impl RoundStatus {
479 pub fn is_final(&self) -> bool {
481 match self {
482 Self::Confirmed { .. } => true,
483 Self::Unconfirmed { .. } => false,
484 Self::Pending { .. } => false,
485 Self::Failed { .. } => true,
486 }
487 }
488
489 pub fn is_success(&self) -> bool {
491 match self {
492 Self::Confirmed { .. } => true,
493 Self::Unconfirmed { .. } => true,
494 Self::Pending { .. } => false,
495 Self::Failed { .. } => false,
496 }
497 }
498}
499
500#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
501#[cfg_attr(feature = "utoipa", derive(ToSchema))]
502pub struct InvoiceInfo {
503 pub invoice: String,
505}
506
507#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
508#[cfg_attr(feature = "utoipa", derive(ToSchema))]
509pub struct LightningReceiveInfo {
510 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
512 pub payment_hash: PaymentHash,
513 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
515 pub payment_preimage: Preimage,
516 pub preimage_revealed_at: Option<chrono::DateTime<chrono::Utc>>,
518 pub invoice: String,
520 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<WalletVtxoInfo>, nullable = true))]
524 pub htlc_vtxos: Option<Vec<WalletVtxoInfo>>,
525}
526
527impl From<bark::persist::models::LightningReceive> for LightningReceiveInfo {
528 fn from(v: bark::persist::models::LightningReceive) -> Self {
529 LightningReceiveInfo {
530 payment_hash: v.payment_hash,
531 payment_preimage: v.payment_preimage,
532 preimage_revealed_at: v.preimage_revealed_at.map(|ts| {
533 chrono::DateTime::from_timestamp_secs(ts as i64)
534 .expect("timestamp is valid")
535 }),
536 invoice: v.invoice.to_string(),
537 htlc_vtxos: v.htlc_vtxos.map(|vtxos| vtxos.into_iter()
538 .map(crate::primitives::WalletVtxoInfo::from).collect()),
539 }
540 }
541}
542
543#[cfg(test)]
544mod test {
545 use super::*;
546
547 #[test]
548 fn ark_info_fields() {
549 #[allow(unused)]
553 fn convert(j: ArkInfo) -> ark::ArkInfo {
554 ark::ArkInfo {
555 network: j.network,
556 server_pubkey: j.server_pubkey,
557 round_interval: j.round_interval,
558 nb_round_nonces: j.nb_round_nonces,
559 vtxo_exit_delta: j.vtxo_exit_delta,
560 vtxo_expiry_delta: j.vtxo_expiry_delta,
561 htlc_send_expiry_delta: j.htlc_send_expiry_delta,
562 htlc_expiry_delta: j.htlc_expiry_delta,
563 max_vtxo_amount: j.max_vtxo_amount,
564 max_arkoor_depth: j.max_arkoor_depth,
565 required_board_confirmations: j.required_board_confirmations,
566 max_user_invoice_cltv_delta: j.max_user_invoice_cltv_delta,
567 min_board_amount: j.min_board_amount,
568 offboard_feerate: FeeRate::from_sat_per_kwu(j.offboard_feerate_sat_per_kvb / 4),
569 ln_receive_anti_dos_required: j.ln_receive_anti_dos_required,
570 }
571 }
572 }
573}
574