Skip to main content

bark_json/cli/
mod.rs

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	/// The bitcoin network the server operates on
34	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
35	pub network: bitcoin::Network,
36	/// The Ark server pubkey
37	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
38	pub server_pubkey: PublicKey,
39	/// The pubkey used for blinding unified mailbox IDs
40	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
41	pub mailbox_pubkey: PublicKey,
42	/// The interval between each round
43	#[serde(with = "serde_utils::duration")]
44	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
45	pub round_interval: Duration,
46	/// Number of nonces per round
47	pub nb_round_nonces: usize,
48	/// Delta between exit confirmation and coins becoming spendable
49	pub vtxo_exit_delta: BlockDelta,
50	/// Expiration delta of the VTXO
51	pub vtxo_expiry_delta: BlockDelta,
52	/// The number of blocks after which an HTLC-send VTXO expires once granted.
53	pub htlc_send_expiry_delta: BlockDelta,
54	/// The number of blocks to keep between Lightning and Ark HTLCs expiries
55	pub htlc_expiry_delta: BlockDelta,
56	/// Maximum amount of a VTXO
57	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
58	pub max_vtxo_amount: Option<Amount>,
59	/// The number of confirmations required to register a board vtxo
60	pub required_board_confirmations: usize,
61	/// Maximum CLTV delta server will allow clients to request an
62	/// invoice generation with.
63	pub max_user_invoice_cltv_delta: u16,
64	/// Minimum amount for a board the server will cosign
65	#[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	/// offboard feerate in sat per kvb
69	pub offboard_feerate_sat_per_kvb: u64,
70	/// Indicates whether the Ark server requires clients to either
71	/// provide a VTXO ownership proof, or a lightning receive token
72	/// when preparing a lightning claim.
73	pub ln_receive_anti_dos_required: bool,
74	/// The fee schedule outlining any fees that must be paid to interact with the Ark server.
75	pub fees: FeeSchedule,
76}
77
78#[derive(Debug, Clone, Deserialize, Serialize)]
79#[cfg_attr(feature = "utoipa", derive(ToSchema))]
80pub struct NextRoundStart {
81	/// The next round start time in RFC 3339 format
82	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/// The different balances of a Bark wallet, broken down by state.
110///
111/// All amounts are in sats.
112#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
113#[cfg_attr(feature = "utoipa", derive(ToSchema))]
114pub struct Balance {
115	/// Sats that are immediately spendable, either in-round or
116	/// out-of-round.
117	#[serde(rename = "spendable_sat", with = "bitcoin::amount::serde::as_sat")]
118	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
119	pub spendable: Amount,
120	/// Sats locked in an outgoing Lightning payment that has not yet
121	/// settled.
122	#[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	/// Sats from an incoming Lightning payment that can be claimed but
126	/// have not yet been swept into a spendable VTXO.
127	#[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	/// Sats locked in VTXOs forfeited for a round that has not yet
131	/// completed.
132	#[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	/// Sats in board transactions that are waiting for sufficient
136	/// on-chain confirmations before becoming spendable.
137	#[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	/// Sats in VTXOs undergoing an emergency exit back on-chain.
141	/// `null` if the exit subsystem is unavailable.
142	#[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	/// Status of each pending exit transaction
169	pub exits: Vec<ExitProgressStatus>,
170	/// Whether all transactions have been confirmed
171	pub done: bool,
172	/// Block height at which all exit outputs will be spendable
173	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	/// The ID of the VTXO that is being unilaterally exited
180	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
181	pub vtxo_id: VtxoId,
182	/// The current state of the exit transaction
183	pub state: ExitState,
184	/// Any error that occurred during the exit process
185	#[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	/// The ID of the VTXO that is being unilaterally exited
203	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
204	pub vtxo_id: VtxoId,
205	/// The current state of the exit transaction
206	pub state: ExitState,
207	/// The history of each state the exit transaction has gone through
208	#[serde(default, skip_serializing_if = "Option::is_none")]
209	pub history: Option<Vec<ExitState>>,
210	/// Each exit transaction package required for the unilateral exit
211	#[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/// Describes a completed transition of funds from onchain to offchain.
227#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
228#[cfg_attr(feature = "utoipa", derive(ToSchema))]
229pub struct PendingBoardInfo {
230	/// The funding transaction.
231	/// This is the transaction that has to be confirmed
232	/// onchain for the board to succeed.
233	pub funding_tx: TransactionInfo,
234	/// The IDs of the VTXOs that were created
235	/// in this board.
236	///
237	/// Currently, this is always a vector of length 1
238	#[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
239	pub vtxos: Vec<VtxoId>,
240	/// The amount of the board.
241	#[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
242	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
243	pub amount: Amount,
244	/// The ID of the movement associated with this board.
245	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	/// The default status of a new [Movement]. Should be treated as in-progress.
264	Pending,
265	/// The [Movement] has completed successfully.
266	Successful,
267	/// The [Movement] failed to complete due to an error. Note; this does not mean that VTXOs or
268	/// user funds didn't change, old VTXOs may be consumed and new ones produced.
269	Failed,
270	/// A [Movement] was canceled, either by the protocol (e.g., lightning payments) or by the
271	/// user.
272	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/// Describes an attempted movement of offchain funds within the [bark::Wallet].
287#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
288#[cfg_attr(feature = "utoipa", derive(ToSchema))]
289pub struct Movement {
290	/// The internal ID of the movement.
291	#[cfg_attr(feature = "utoipa", schema(value_type = u32))]
292	pub id: MovementId,
293	/// The status of the movement.
294	pub status: MovementStatus,
295	/// Contains information about the subsystem that created the movement as well as the purpose
296	/// of the movement.
297	pub subsystem: MovementSubsystem,
298	/// Miscellaneous metadata for the movement. This is JSON containing arbitrary information as
299	/// defined by the subsystem that created the movement.
300	#[serde(default, skip_serializing_if = "Option::is_none")]
301	pub metadata: Option<HashMap<String, serde_json::Value>>,
302	/// How much the movement was expected to increase or decrease the balance by. This is always an
303	/// estimate and often discounts any applicable fees.
304	#[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	/// How much the wallet balance actually changed by. Positive numbers indicate an increase and
308	/// negative numbers indicate a decrease. This is often inclusive of applicable fees, and it
309	/// should be the most accurate number.
310	#[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	/// How much the movement cost the user in offchain fees. If there are applicable onchain fees
314	/// they will not be included in this value but, depending on the subsystem, could be found in
315	/// the metadata.
316	#[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	/// A list of external recipients that received funds from this movement.
320	pub sent_to: Vec<MovementDestination>,
321	/// Describes the means by which the wallet received funds in this movement. This could include
322	/// BOLT11 invoices or other useful data.
323	pub received_on: Vec<MovementDestination>,
324	/// A list of [Vtxo](ark::Vtxo) IDs that were consumed by this movement and
325	/// are either locked or unavailable.
326	#[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
327	pub input_vtxos: Vec<VtxoId>,
328	/// A list of IDs for new VTXOs that were produced as a result of this movement. Often change
329	/// VTXOs will be found here for outbound actions unless this was an inbound action.
330	#[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
331	pub output_vtxos: Vec<VtxoId>,
332	/// A list of IDs for VTXOs that were marked for unilateral exit as a result of this movement.
333	/// This could happen for many reasons, e.g. an unsuccessful lightning payment which can't be
334	/// revoked but is about to expire. VTXOs listed here will result in a reduction of spendable
335	/// balance due to the VTXOs being managed by the [bark::exit::Exit] system.
336	#[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
337	pub exited_vtxos: Vec<VtxoId>,
338	/// Contains the times at which the movement was created, updated and completed.
339	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/// Describes a recipient of a movement. This could either be an external recipient in send actions
363/// or it could be the bark wallet itself.
364#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
365#[cfg_attr(feature = "utoipa", derive(ToSchema))]
366pub struct MovementDestination {
367	/// An address, invoice or any other identifier to distinguish the recipient.
368	pub destination: PaymentMethod,
369	/// How many sats the recipient received.
370	#[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/// Provides a typed mechanism for describing the recipient in a [MovementDestination].
385/// This is a bark-json wrapper that serializes all payment methods as strings for utoipa
386/// compatibility.
387#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
388#[serde(tag = "type", content = "value", rename_all = "kebab-case")]
389pub enum PaymentMethod {
390	/// An [ark::Address] format for bark.
391	Ark(String),
392	/// An onchain [bitcoin::Address].
393	Bitcoin(String),
394	/// An onchain [bitcoin::ScriptBuf] output, typically used for non-address formats like
395	/// OP_RETURN.
396	OutputScript(String),
397	/// Any supported form of lightning invoice, e.g., BOLT11 and BOLT12.
398	Invoice(String),
399	/// A reusable BOLT12 offer for making lightning payments.
400	Offer(String),
401	/// A variant using an email-like lightning address format.
402	LightningAddress(String),
403	/// An alternative payment method that isn't native to bark.
404	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/// Contains information about the subsystem that created the movement as well as the purpose
492/// of the movement.
493#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
494#[cfg_attr(feature = "utoipa", derive(ToSchema))]
495pub struct MovementSubsystem {
496	/// The name of the subsystem that created and manages the movement.
497	pub name: String,
498	/// The action responsible for registering the movement.
499	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/// Contains the times at which the movement was created, updated and completed.
512#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
513#[cfg_attr(feature = "utoipa", derive(ToSchema))]
514pub struct MovementTimestamp {
515	/// When the movement was first created.
516	pub created_at: DateTime<chrono::Local>,
517	/// When the movement was last updated.
518	pub updated_at: DateTime<chrono::Local>,
519	/// The action responsible for registering the movement.
520	#[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	/// Failed to sync round
539	SyncError {
540		error: String,
541	},
542	/// The round was successful and is fully confirmed
543	Confirmed {
544		#[cfg_attr(feature = "utoipa", schema(value_type = String))]
545		funding_txid: Txid,
546	},
547	/// Round successful but not fully confirmed
548	Unconfirmed {
549		#[cfg_attr(feature = "utoipa", schema(value_type = String))]
550		funding_txid: Txid,
551	},
552	/// We have unsigned funding transactions that might confirm
553	Pending,
554	/// The round failed
555	Failed {
556		error: String,
557	},
558	/// The round canceled
559	Canceled,
560}
561
562impl RoundStatus {
563	/// Whether this is the final state and it won't change anymore
564	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	/// Whether it looks like the round succeeded
576	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	/// The invoice string
614	pub invoice: String,
615}
616
617#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
618#[cfg_attr(feature = "utoipa", derive(ToSchema))]
619pub struct OffboardResult {
620	/// The transaction id of the offboard transaction
621	#[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	/// The amount of the lightning receive
629	#[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
630	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
631	pub amount: Amount,
632	/// The payment hash linked to the lightning receive info
633	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
634	pub payment_hash: PaymentHash,
635	/// The payment preimage linked to the lightning receive info
636	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
637	pub payment_preimage: Preimage,
638	/// The timestamp at which the preimage was revealed
639	pub preimage_revealed_at: Option<chrono::DateTime<chrono::Local>>,
640	/// The timestamp at which the lightning receive was finished
641	pub finished_at: Option<chrono::DateTime<chrono::Local>>,
642	/// The invoice string
643	pub invoice: String,
644	/// The HTLC VTXOs granted by the server for the lightning receive
645	///
646	/// Empty if the lightning HTLC has not yet been received by the server.
647	#[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	/// The amount being sent
672	#[serde(rename = "amount_sat", with = "bitcoin::amount::serde::as_sat")]
673	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
674	pub amount: Amount,
675	/// The payment hash linked to the lightning send
676	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
677	pub payment_hash: PaymentHash,
678	/// The invoice string
679	pub invoice: String,
680	/// The payment preimage if the payment has completed successfully
681	#[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
682	pub preimage: Option<Preimage>,
683	/// The HTLC VTXOs used for the lightning send
684	#[cfg_attr(feature = "utoipa", schema(value_type = Vec<WalletVtxoInfo>))]
685	pub htlc_vtxos: Vec<WalletVtxoInfo>,
686	/// When the payment reached a terminal state (succeeded or failed)
687	#[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/// Represents a lightning movement, either a send or receive
706#[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	/// A lightning receive (incoming payment)
711	Receive(LightningReceiveInfo),
712	/// A lightning send (outgoing payment)
713	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		//! the purpose of this test is to fail if we add a field to
755		//! ark::ArkInfo but we forgot to add it to the ArkInfo here
756
757		#[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