bark_json/
cli.rs

1
2use std::borrow::Borrow;
3use std::time::Duration;
4
5use bitcoin::secp256k1::PublicKey;
6use bitcoin::{Address, Amount, FeeRate, Txid, Wtxid, address};
7
8use ark::lightning::{PaymentHash, Preimage};
9use ark::rounds::RoundId;
10use ark::VtxoId;
11use bitcoin_ext::{BlockDelta, BlockHeight};
12#[cfg(feature = "utoipa")]
13use utoipa::ToSchema;
14
15use crate::exit::error::ExitError;
16use crate::exit::package::ExitTransactionPackage;
17use crate::exit::ExitState;
18use crate::primitives::{VtxoInfo, RecipientInfo};
19use crate::{WalletVtxoInfo, serde_utils};
20
21#[derive(Debug, Clone, Deserialize, Serialize)]
22#[cfg_attr(feature = "utoipa", derive(ToSchema))]
23pub struct ArkInfo {
24	/// The bitcoin network the server operates on
25	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
26	pub network: bitcoin::Network,
27	/// The Ark server pubkey
28	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
29	pub server_pubkey: PublicKey,
30	/// The interval between each round
31	#[serde(with = "serde_utils::duration")]
32	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
33	pub round_interval: Duration,
34	/// Number of nonces per round
35	pub nb_round_nonces: usize,
36	/// Delta between exit confirmation and coins becoming spendable
37	pub vtxo_exit_delta: BlockDelta,
38	/// Expiration delta of the VTXO
39	pub vtxo_expiry_delta: BlockDelta,
40	/// The number of blocks after which an HTLC-send VTXO expires once granted.
41	pub htlc_send_expiry_delta: BlockDelta,
42	/// The number of blocks to keep between Lightning and Ark HTLCs expiries
43	pub htlc_expiry_delta: BlockDelta,
44	/// Maximum amount of a VTXO
45	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
46	pub max_vtxo_amount: Option<Amount>,
47	/// Maximum number of OOR transition after VTXO tree leaf
48	pub max_arkoor_depth: u16,
49	/// The number of confirmations required to register a board vtxo
50	pub required_board_confirmations: usize,
51	/// Maximum CLTV delta server will allow clients to request an
52	/// invoice generation with.
53	pub max_user_invoice_cltv_delta: u16,
54	/// Minimum amount for a board the server will cosign
55	pub min_board_amount: Amount,
56}
57
58impl<T: Borrow<ark::ArkInfo>> From<T> for ArkInfo {
59	fn from(v: T) -> Self {
60		let v = v.borrow();
61	    ArkInfo {
62			network: v.network,
63			server_pubkey: v.server_pubkey,
64			round_interval: v.round_interval,
65			nb_round_nonces: v.nb_round_nonces,
66			vtxo_exit_delta: v.vtxo_exit_delta,
67			vtxo_expiry_delta: v.vtxo_expiry_delta,
68			htlc_send_expiry_delta: v.htlc_send_expiry_delta,
69			htlc_expiry_delta: v.htlc_expiry_delta,
70			max_vtxo_amount: v.max_vtxo_amount,
71			max_arkoor_depth: v.max_arkoor_depth,
72			required_board_confirmations: v.required_board_confirmations,
73			max_user_invoice_cltv_delta: v.max_user_invoice_cltv_delta,
74			min_board_amount: v.min_board_amount,
75		}
76	}
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
80#[cfg_attr(feature = "utoipa", derive(ToSchema))]
81pub struct LightningReceiveBalance {
82	#[serde(rename = "total_sat", with = "bitcoin::amount::serde::as_sat")]
83	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
84	pub total: Amount,
85	#[serde(rename = "claimable_sat", with = "bitcoin::amount::serde::as_sat")]
86	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
87	pub claimable: Amount,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
91#[cfg_attr(feature = "utoipa", derive(ToSchema))]
92pub struct Balance {
93	#[serde(rename = "spendable_sat", with = "bitcoin::amount::serde::as_sat")]
94	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
95	pub spendable: Amount,
96	#[serde(rename = "pending_lightning_send_sat", with = "bitcoin::amount::serde::as_sat")]
97	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
98	pub pending_lightning_send: Amount,
99	pub pending_lightning_receive: LightningReceiveBalance,
100	#[serde(rename = "pending_in_round_sat", with = "bitcoin::amount::serde::as_sat")]
101	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
102	pub pending_in_round: Amount,
103	#[serde(rename = "pending_board_sat", with = "bitcoin::amount::serde::as_sat")]
104	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
105	pub pending_board: Amount,
106	#[serde(
107		default,
108		rename = "pending_exit_sat",
109		with = "bitcoin::amount::serde::as_sat::opt",
110		skip_serializing_if = "Option::is_none",
111	)]
112	#[cfg_attr(feature = "utoipa", schema(value_type = u64, nullable=true))]
113	pub pending_exit: Option<Amount>,
114}
115
116#[derive(Debug, Clone, Deserialize, Serialize)]
117#[cfg_attr(feature = "utoipa", derive(ToSchema))]
118pub struct Config {
119	/// Ark server address
120	pub ark: String,
121	/// Bitcoin Core RPC address to use for syncing
122	pub bitcoind: Option<String>,
123	/// Cookie to use for RPC authentication
124	pub bitcoind_cookie: Option<String>,
125	/// Username to use for RPC authentication
126	pub bitcoind_user: Option<String>,
127	/// password to use for RPC authentication
128	pub bitcoind_pass: Option<String>,
129	/// The Esplora REST API address to use for syncing
130	pub esplora: Option<String>,
131	/// How many blocks before VTXO expiration before preemptively refreshing them
132	pub vtxo_refresh_expiry_threshold: BlockHeight,
133	#[serde(rename = "fallback_fee_rate_kvb", with = "serde_utils::fee_rate_sats_per_kvb")]
134	#[cfg_attr(feature = "utoipa", schema(value_type = u64, nullable = true))]
135	pub fallback_fee_rate: Option<FeeRate>,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
139#[cfg_attr(feature = "utoipa", derive(ToSchema))]
140pub struct ExitProgressResponse {
141	/// Status of each pending exit transaction
142	pub exits: Vec<ExitProgressStatus>,
143	/// Whether all transactions have been confirmed
144	pub done: bool,
145	/// Block height at which all exit outputs will be spendable
146	pub claimable_height: Option<u32>,
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
150#[cfg_attr(feature = "utoipa", derive(ToSchema))]
151pub struct ExitProgressStatus {
152	/// The ID of the VTXO that is being unilaterally exited
153	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
154	pub vtxo_id: VtxoId,
155	/// The current state of the exit transaction
156	pub state: ExitState,
157	/// Any error that occurred during the exit process
158	#[serde(default, skip_serializing_if = "Option::is_none")]
159	pub error: Option<ExitError>,
160}
161
162impl From<bark::exit::models::ExitProgressStatus> for ExitProgressStatus {
163	fn from(v: bark::exit::models::ExitProgressStatus) -> Self {
164		ExitProgressStatus {
165			vtxo_id: v.vtxo_id,
166			state: v.state.into(),
167			error: v.error.map(ExitError::from),
168		}
169	}
170}
171
172#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
173#[cfg_attr(feature = "utoipa", derive(ToSchema))]
174pub struct ExitTransactionStatus {
175	/// The ID of the VTXO that is being unilaterally exited
176	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
177	pub vtxo_id: VtxoId,
178	/// The current state of the exit transaction
179	pub state: ExitState,
180	/// The history of each state the exit transaction has gone through
181	#[serde(default, skip_serializing_if = "Option::is_none")]
182	pub history: Option<Vec<ExitState>>,
183	/// Each exit transaction package required for the unilateral exit
184	#[serde(default, skip_serializing_if = "Vec::is_empty")]
185	pub transactions: Vec<ExitTransactionPackage>,
186}
187
188impl From<bark::exit::models::ExitTransactionStatus> for ExitTransactionStatus {
189	fn from(v: bark::exit::models::ExitTransactionStatus) -> Self {
190		ExitTransactionStatus {
191			vtxo_id: v.vtxo_id,
192			state: v.state.into(),
193			history: v.history.map(|h| h.into_iter().map(ExitState::from).collect()),
194			transactions: v.transactions.into_iter().map(ExitTransactionPackage::from).collect(),
195		}
196	}
197}
198
199/// Describes a completed transition of funds from onchain to offchain.
200#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
201#[cfg_attr(feature = "utoipa", derive(ToSchema))]
202pub struct Board {
203	/// The [Txid] of the funding-transaction.
204	/// This is the transaction that has to be confirmed
205	/// onchain for the board to succeed.
206	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
207	pub funding_txid: Txid,
208	/// The info for each [ark::Vtxo] that was created
209	/// in this board.
210	///
211	/// Currently, this is always a vector of length 1
212	pub vtxos: Vec<VtxoInfo>,
213}
214
215impl From<bark::Board> for Board {
216	fn from(v: bark::Board) -> Self {
217		Board {
218			funding_txid: v.funding_txid,
219			vtxos: v.vtxos.into_iter().map(VtxoInfo::from).collect(),
220		}
221	}
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
225#[cfg_attr(feature = "utoipa", derive(ToSchema))]
226pub struct Movement {
227	pub id: u32,
228	/// Fees paid for the movement
229	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
230	pub fees: Amount,
231	/// wallet's VTXOs spent in this movement
232	pub spends: Vec<VtxoInfo>,
233	/// Received VTXOs from this movement
234	pub receives: Vec<VtxoInfo>,
235	/// External recipients of the movement
236	pub recipients: Vec<RecipientInfo>,
237	/// Movement date
238	pub created_at: String,
239}
240
241
242pub mod onchain {
243	use super::*;
244
245	#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
246	#[cfg_attr(feature = "utoipa", derive(ToSchema))]
247	pub struct Send {
248		#[cfg_attr(feature = "utoipa", schema(value_type = String))]
249		pub txid: Txid,
250	}
251
252	#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
253	#[cfg_attr(feature = "utoipa", derive(ToSchema))]
254	pub struct Address {
255		#[cfg_attr(feature = "utoipa", schema(value_type = String))]
256		pub address: bitcoin::Address<bitcoin::address::NetworkUnchecked>,
257	}
258
259	#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
260	#[cfg_attr(feature = "utoipa", derive(ToSchema))]
261	pub struct OnchainBalance {
262		/// All of them combined.
263		#[serde(rename="total_sat", with="bitcoin::amount::serde::as_sat")]
264		#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
265		pub total: Amount,
266		/// Get sum of trusted_pending and confirmed coins.
267		///
268		/// This is the balance you can spend right now that shouldn't get cancelled via another party
269		/// double spending it.
270		#[serde(rename="trusted_spendable_sat", with="bitcoin::amount::serde::as_sat")]
271		#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
272		pub trusted_spendable: Amount,
273		/// All coinbase outputs not yet matured
274		#[serde(rename="immature_sat", with="bitcoin::amount::serde::as_sat")]
275		#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
276		pub immature: Amount,
277		/// Unconfirmed UTXOs generated by a wallet tx
278		#[serde(rename="trusted_pending_sat", with="bitcoin::amount::serde::as_sat")]
279		#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
280		pub trusted_pending: Amount,
281		/// Unconfirmed UTXOs received from an external wallet
282		#[serde(rename="untrusted_pending_sat", with="bitcoin::amount::serde::as_sat")]
283		#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
284		pub untrusted_pending: Amount,
285		/// Confirmed and immediately spendable balance
286		#[serde(rename="confirmed_sat", with="bitcoin::amount::serde::as_sat")]
287		#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
288		pub confirmed: Amount,
289	}
290}
291
292/// Describes a completed transition of funds from offchain to onchain collaboratively with the
293/// Ark server.
294#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
295#[cfg_attr(feature = "utoipa", derive(ToSchema))]
296pub struct Offboard {
297	/// The [RoundId] of the round in which the offboard occurred
298	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
299	pub round: RoundId,
300	// TODO: List the [OutPoint] and [Amount] here
301}
302
303impl From<bark::Offboard> for Offboard {
304	fn from(v: bark::Offboard) -> Self {
305		Offboard { round: v.round }
306	}
307}
308
309/// The output of the `bark refresh` command
310#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
311#[cfg_attr(feature = "utoipa", derive(ToSchema))]
312pub struct Refresh {
313	/// A boolean indicated if the command participated
314	/// in a round. If no [ark::Vtxo] was refreshed this variable
315	/// will be set to [false] and otherwise [true]
316	pub participate_round: bool,
317	/// The [RoundId] of the round if the client participated in a round
318	#[cfg_attr(feature = "utoipa", schema(value_type = String, nullable = true))]
319	pub round: Option<RoundId>,
320}
321
322#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
323#[cfg_attr(feature = "utoipa", derive(ToSchema))]
324pub struct InvoiceInfo {
325	/// The invoice string
326	pub invoice: String,
327}
328
329#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
330#[cfg_attr(feature = "utoipa", derive(ToSchema))]
331pub struct LightningReceiveInfo {
332	/// The payment hash linked to the lightning receive info
333	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
334	pub payment_hash: PaymentHash,
335	/// The payment preimage linked to the lightning receive info
336	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
337	pub payment_preimage: Preimage,
338	/// The timestamp at which the preimage was revealed
339	pub preimage_revealed_at: Option<chrono::DateTime<chrono::Utc>>,
340	/// The invoice string
341	pub invoice: String,
342	/// The HTLC VTXOs granted by the server for the lightning receive
343	///
344	/// Only present if the lightning HTLC has been received by the server.
345	#[cfg_attr(feature = "utoipa", schema(value_type = Vec<WalletVtxoInfo>, nullable = true))]
346	pub htlc_vtxos: Option<Vec<WalletVtxoInfo>>,
347}
348
349impl From<bark::persist::models::LightningReceive> for LightningReceiveInfo {
350	fn from(v: bark::persist::models::LightningReceive) -> Self {
351		LightningReceiveInfo {
352			payment_hash: v.payment_hash,
353			payment_preimage: v.payment_preimage,
354			preimage_revealed_at: v.preimage_revealed_at.map(|ts| {
355				chrono::DateTime::from_timestamp_secs(ts as i64)
356					.expect("timestamp is valid")
357			}),
358			invoice: v.invoice.to_string(),
359			htlc_vtxos: v.htlc_vtxos.map(|vtxos| vtxos.into_iter()
360				.map(crate::primitives::WalletVtxoInfo::from).collect()),
361		}
362	}
363}
364
365#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
366#[cfg_attr(feature = "utoipa", derive(ToSchema))]
367pub struct InputScriptInfo {
368	pub hex: Option<Vec<u8>>,
369	pub asm: Option<String>,
370}
371
372impl From<hal::tx::InputScriptInfo> for InputScriptInfo {
373	fn from(v: hal::tx::InputScriptInfo) -> Self {
374		InputScriptInfo {
375			hex: v.hex.map(|hex| hex.bytes().to_vec()),
376			asm: v.asm,
377		}
378	}
379}
380
381#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
382#[cfg_attr(feature = "utoipa", derive(ToSchema))]
383pub struct InputInfo {
384	pub prevout: Option<String>,
385	#[cfg_attr(feature = "utoipa", schema(value_type = String, nullable = true))]
386	pub txid: Option<Txid>,
387	pub vout: Option<u32>,
388	pub script_sig: Option<InputScriptInfo>,
389	pub sequence: Option<u32>,
390	pub witness: Option<Vec<Vec<u8>>>,
391}
392
393impl From<hal::tx::InputInfo> for InputInfo {
394	fn from(v: hal::tx::InputInfo) -> Self {
395		InputInfo {
396			prevout: v.prevout,
397			txid: v.txid,
398			vout: v.vout,
399			script_sig: v.script_sig.map(InputScriptInfo::from),
400			sequence: v.sequence,
401			witness: v.witness.map(|witness| witness.into_iter().map(|witness| witness.bytes().to_vec()).collect()),
402		}
403	}
404}
405
406#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
407#[cfg_attr(feature = "utoipa", derive(ToSchema))]
408pub struct OutputScriptInfo {
409	pub hex: Option<Vec<u8>>,
410	pub asm: Option<String>,
411	#[serde(skip_serializing_if = "Option::is_none", rename = "type")]
412	pub type_: Option<String>,
413	#[serde(skip_serializing_if = "Option::is_none")]
414	#[cfg_attr(feature = "utoipa", schema(value_type = String, nullable = true))]
415	pub address: Option<Address<address::NetworkUnchecked>>,
416}
417
418impl From<hal::tx::OutputScriptInfo> for OutputScriptInfo {
419	fn from(v: hal::tx::OutputScriptInfo) -> Self {
420		OutputScriptInfo {
421			hex: v.hex.map(|hex| hex.bytes().to_vec()),
422			asm: v.asm,
423			type_: v.type_,
424			address: v.address,
425		}
426	}
427}
428
429#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
430#[cfg_attr(feature = "utoipa", derive(ToSchema))]
431pub struct OutputInfo {
432	#[cfg_attr(feature = "utoipa", schema(value_type = u64, nullable = true))]
433	pub value: Option<Amount>,
434	pub script_pub_key: Option<OutputScriptInfo>,
435}
436
437impl From<hal::tx::OutputInfo> for OutputInfo {
438	fn from(v: hal::tx::OutputInfo) -> Self {
439		OutputInfo {
440			value: v.value,
441			script_pub_key: v.script_pub_key.map(OutputScriptInfo::from),
442		}
443	}
444}
445
446#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
447#[cfg_attr(feature = "utoipa", derive(ToSchema))]
448pub struct TransactionInfo {
449	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
450	pub txid: Option<Txid>,
451	#[cfg_attr(feature = "utoipa", schema(value_type = String))]
452	pub wtxid: Option<Wtxid>,
453	pub size: Option<usize>,
454	pub weight: Option<usize>,
455	pub vsize: Option<usize>,
456	pub version: Option<i32>,
457	pub locktime: Option<u32>,
458	pub inputs: Option<Vec<InputInfo>>,
459	pub outputs: Option<Vec<OutputInfo>>,
460	pub total_output_value: Option<u64>,
461}
462
463impl From<hal::tx::TransactionInfo> for TransactionInfo {
464	fn from(v: hal::tx::TransactionInfo) -> Self {
465		TransactionInfo {
466			txid: v.txid,
467			wtxid: v.wtxid,
468			size: v.size,
469			weight: v.weight,
470			vsize: v.vsize,
471			version: v.version,
472			locktime: v.locktime,
473			inputs: v.inputs.map(|inputs| {
474				inputs.into_iter().map(InputInfo::from).collect()
475			}),
476			outputs: v.outputs.map(|outputs| {
477				outputs.into_iter().map(OutputInfo::from).collect()
478			}),
479			total_output_value: v.total_output_value,
480		}
481	}
482}
483
484#[cfg(test)]
485mod test {
486	use super::*;
487
488	#[test]
489	fn ark_info_fields() {
490		//! the purpose of this test is to fail if we add a field to
491		//! ark::ArkInfo but we forgot to add it to the ArkInfo here
492
493		#[allow(unused)]
494		fn convert(j: ArkInfo) -> ark::ArkInfo {
495			ark::ArkInfo {
496				network: j.network,
497				server_pubkey: j.server_pubkey,
498				round_interval: j.round_interval,
499				nb_round_nonces: j.nb_round_nonces,
500				vtxo_exit_delta: j.vtxo_exit_delta,
501				vtxo_expiry_delta: j.vtxo_expiry_delta,
502				htlc_send_expiry_delta: j.htlc_send_expiry_delta,
503				htlc_expiry_delta: j.htlc_expiry_delta,
504				max_vtxo_amount: j.max_vtxo_amount,
505				max_arkoor_depth: j.max_arkoor_depth,
506				required_board_confirmations: j.required_board_confirmations,
507				max_user_invoice_cltv_delta: j.max_user_invoice_cltv_delta,
508				min_board_amount: j.min_board_amount,
509			}
510		}
511	}
512}