Skip to main content

bark_json/
movements.rs

1use std::str::FromStr;
2
3use anyhow::anyhow;
4use bitcoin::{Amount, ScriptBuf, SignedAmount};
5use chrono::DateTime;
6
7use ark::VtxoId;
8use ark::lightning::{Invoice, Offer};
9use bark::lnurllib::lightning_address::LightningAddress;
10use bark::lnurllib::lnurl::LnUrl;
11use bark::movement::MovementId;
12
13
14#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
15#[serde(rename_all = "kebab-case")]
16#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
17pub enum MovementStatus {
18	/// The default status of a new [Movement]. Should be treated as in-progress.
19	Pending,
20	/// The [Movement] has completed successfully.
21	Successful,
22	/// The [Movement] failed to complete due to an error. Note; this does not mean that VTXOs or
23	/// user funds didn't change, old VTXOs may be consumed and new ones produced.
24	Failed,
25	/// A [Movement] was canceled, either by the protocol (e.g., lightning payments) or by the
26	/// user.
27	Canceled,
28}
29
30impl From<bark::movement::MovementStatus> for MovementStatus {
31	fn from(v: bark::movement::MovementStatus) -> Self {
32		match v {
33			bark::movement::MovementStatus::Pending => Self::Pending,
34			bark::movement::MovementStatus::Successful => Self::Successful,
35			bark::movement::MovementStatus::Failed => Self::Failed,
36			bark::movement::MovementStatus::Canceled => Self::Canceled,
37		}
38	}
39}
40
41/// Describes an attempted movement of offchain funds within the [bark::Wallet].
42#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
43#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
44pub struct Movement {
45	/// The internal ID of the movement.
46	#[cfg_attr(feature = "utoipa", schema(value_type = u32))]
47	pub id: MovementId,
48	/// The status of the movement.
49	pub status: MovementStatus,
50	/// Contains information about the subsystem that created the movement as well as the purpose
51	/// of the movement.
52	pub subsystem: MovementSubsystem,
53	/// Miscellaneous metadata for the movement. This is JSON containing arbitrary information as
54	/// defined by the subsystem that created the movement.
55	#[serde(default, skip_serializing_if = "Option::is_none")]
56	pub metadata: Option<serde_json::Map<String, serde_json::Value>>,
57	/// How much the movement was expected to increase or decrease the balance by. This is always an
58	/// estimate and often discounts any applicable fees.
59	#[serde(rename="intended_balance_sat", with="bitcoin::amount::serde::as_sat")]
60	#[cfg_attr(feature = "utoipa", schema(value_type = i64))]
61	pub intended_balance: SignedAmount,
62	/// How much the wallet balance actually changed by. Positive numbers indicate an increase and
63	/// negative numbers indicate a decrease. This is often inclusive of applicable fees, and it
64	/// should be the most accurate number.
65	#[serde(rename="effective_balance_sat", with="bitcoin::amount::serde::as_sat")]
66	#[cfg_attr(feature = "utoipa", schema(value_type = i64))]
67	pub effective_balance: SignedAmount,
68	/// How much the movement cost the user in offchain fees. If there are applicable onchain fees
69	/// they will not be included in this value but, depending on the subsystem, could be found in
70	/// the metadata.
71	#[serde(rename="offchain_fee_sat", with="bitcoin::amount::serde::as_sat")]
72	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
73	pub offchain_fee: Amount,
74	/// A list of external recipients that received funds from this movement.
75	pub sent_to: Vec<MovementDestination>,
76	/// Describes the means by which the wallet received funds in this movement. This could include
77	/// BOLT11 invoices or other useful data.
78	pub received_on: Vec<MovementDestination>,
79	/// A list of [Vtxo](ark::Vtxo) IDs that were consumed by this movement and
80	/// are either locked or unavailable.
81	#[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
82	pub input_vtxos: Vec<VtxoId>,
83	/// A list of IDs for new VTXOs that were produced as a result of this movement. Often change
84	/// VTXOs will be found here for outbound actions unless this was an inbound action.
85	#[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
86	pub output_vtxos: Vec<VtxoId>,
87	/// A list of IDs for VTXOs that were marked for unilateral exit as a result of this movement.
88	/// This could happen for many reasons, e.g. an unsuccessful lightning payment which can't be
89	/// revoked but is about to expire. VTXOs listed here will result in a reduction of spendable
90	/// balance due to the VTXOs being managed by the [bark::exit::Exit] system.
91	#[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
92	pub exited_vtxos: Vec<VtxoId>,
93	/// Contains the times at which the movement was created, updated and completed.
94	pub time: MovementTimestamp,
95}
96
97impl From<bark::movement::Movement> for Movement {
98	fn from(m: bark::movement::Movement) -> Self {
99		Movement {
100			id: m.id,
101			status: m.status.into(),
102			subsystem: MovementSubsystem::from(m.subsystem),
103			metadata: if m.metadata.is_empty() { None } else { Some(m.metadata) },
104			intended_balance: m.intended_balance,
105			effective_balance: m.effective_balance,
106			offchain_fee: m.offchain_fee,
107			sent_to: m.sent_to.into_iter().map(MovementDestination::from).collect(),
108			received_on: m.received_on.into_iter().map(MovementDestination::from).collect(),
109			input_vtxos: m.input_vtxos,
110			output_vtxos: m.output_vtxos,
111			exited_vtxos: m.exited_vtxos,
112			time: MovementTimestamp::from(m.time),
113		}
114	}
115}
116
117/// Describes a recipient of a movement. This could either be an external recipient in send actions
118/// or it could be the bark wallet itself.
119#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
120#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
121pub struct MovementDestination {
122	/// An address, invoice or any other identifier to distinguish the recipient.
123	pub destination: PaymentMethod,
124	/// How many sats the recipient received.
125	#[serde(rename="amount_sat", with="bitcoin::amount::serde::as_sat")]
126	#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
127	pub amount: Amount,
128}
129
130impl From<bark::movement::MovementDestination> for MovementDestination {
131	fn from(d: bark::movement::MovementDestination) -> Self {
132		MovementDestination {
133			destination: PaymentMethod::from(d.destination),
134			amount: d.amount,
135		}
136	}
137}
138
139/// Provides a typed mechanism for describing the recipient in a [MovementDestination].
140/// This is a bark-json wrapper that serializes all payment methods as strings for utoipa
141/// compatibility.
142#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
143#[serde(tag = "type", content = "value", rename_all = "kebab-case")]
144pub enum PaymentMethod {
145	/// An [ark::Address] format for bark.
146	Ark(String),
147	/// An onchain [bitcoin::Address].
148	Bitcoin(String),
149	/// An onchain [bitcoin::ScriptBuf] output, typically used for non-address formats like
150	/// OP_RETURN.
151	OutputScript(String),
152	/// Any supported form of lightning invoice, e.g., BOLT11 and BOLT12.
153	Invoice(String),
154	/// A reusable BOLT12 offer for making lightning payments.
155	Offer(String),
156	/// A variant using an email-like lightning address format.
157	LightningAddress(String),
158	/// A bech32-encoded LNURL-pay link (`lnurl1…`).
159	Lnurl(String),
160	/// An alternative payment method that isn't native to bark.
161	Custom(String),
162}
163
164#[cfg(feature = "utoipa")]
165impl utoipa::PartialSchema for PaymentMethod {
166	fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
167		use utoipa::openapi::schema;
168
169		schema::ObjectBuilder::new()
170			.title(Some("PaymentMethod"))
171			.description(Some("A payment method with a type discriminator and string value"))
172			.property(
173				"type",
174				schema::ObjectBuilder::new()
175					.schema_type(schema::SchemaType::Type(schema::Type::String))
176					.enum_values(Some([
177						"ark",
178						"bitcoin",
179						"output-script",
180						"invoice",
181						"offer",
182						"lightning-address",
183						"lnurl",
184						"custom",
185					]))
186					.description(Some("The type of payment method"))
187			)
188			.required("type")
189			.property(
190				"value",
191				schema::ObjectBuilder::new()
192					.schema_type(schema::SchemaType::Type(schema::Type::String))
193					.description(Some("The payment method value (address, invoice, etc.)"))
194			)
195			.required("value")
196			.into()
197	}
198}
199
200#[cfg(feature = "utoipa")]
201impl utoipa::ToSchema for PaymentMethod {
202	fn name() -> std::borrow::Cow<'static, str> {
203		std::borrow::Cow::Borrowed("PaymentMethod")
204	}
205}
206
207impl From<bark::movement::PaymentMethod> for PaymentMethod {
208	fn from(p: bark::movement::PaymentMethod) -> Self {
209		match p {
210			bark::movement::PaymentMethod::Ark(a) => Self::Ark(a.to_string()),
211			bark::movement::PaymentMethod::Bitcoin(b) => Self::Bitcoin(b.assume_checked().to_string()),
212			bark::movement::PaymentMethod::OutputScript(s) => Self::OutputScript(s.to_hex_string()),
213			bark::movement::PaymentMethod::Invoice(i) => Self::Invoice(i.to_string()),
214			bark::movement::PaymentMethod::Offer(o) => Self::Offer(o.to_string()),
215			bark::movement::PaymentMethod::LightningAddress(l) => Self::LightningAddress(l.to_string()),
216			bark::movement::PaymentMethod::Lnurl(l) => Self::Lnurl(l.to_string()),
217			bark::movement::PaymentMethod::Custom(c) => Self::Custom(c),
218		}
219	}
220}
221
222impl TryFrom<PaymentMethod> for bark::movement::PaymentMethod {
223	type Error = anyhow::Error;
224
225	fn try_from(p: PaymentMethod) -> Result<Self, Self::Error> {
226		match p {
227			PaymentMethod::Ark(a) => Ok(bark::movement::PaymentMethod::Ark(
228				ark::Address::from_str(&a)?,
229			)),
230			PaymentMethod::Bitcoin(b) => Ok(bark::movement::PaymentMethod::Bitcoin(
231				bitcoin::Address::from_str(&b)?,
232			)),
233			PaymentMethod::OutputScript(s) => Ok(bark::movement::PaymentMethod::OutputScript(
234				ScriptBuf::from_hex(&s)?,
235			)),
236			PaymentMethod::Invoice(i) => Ok(bark::movement::PaymentMethod::Invoice(
237				Invoice::from_str(&i)?,
238			)),
239			PaymentMethod::Offer(o) => Ok(bark::movement::PaymentMethod::Offer(
240				Offer::from_str(&o).map_err(|e| anyhow!("Failed to parse offer: {:?}", e))?,
241			)),
242			PaymentMethod::LightningAddress(l) => Ok(bark::movement::PaymentMethod::LightningAddress(
243				LightningAddress::from_str(&l)?,
244			)),
245			PaymentMethod::Lnurl(l) => Ok(bark::movement::PaymentMethod::Lnurl(
246				LnUrl::from_str(&l)?,
247			)),
248			PaymentMethod::Custom(c) => Ok(bark::movement::PaymentMethod::Custom(c)),
249		}
250	}
251}
252
253/// Contains information about the subsystem that created the movement as well as the purpose
254/// of the movement.
255#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
256#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
257pub struct MovementSubsystem {
258	/// The name of the subsystem that created and manages the movement.
259	pub name: String,
260	/// The action responsible for registering the movement.
261	pub kind: String,
262}
263
264impl From<bark::movement::MovementSubsystem> for MovementSubsystem {
265	fn from(s: bark::movement::MovementSubsystem) -> Self {
266		MovementSubsystem {
267			name: s.name,
268			kind: s.kind,
269		}
270	}
271}
272
273/// Contains the times at which the movement was created, updated and completed.
274#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
275#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
276pub struct MovementTimestamp {
277	/// When the movement was first created.
278	pub created_at: DateTime<chrono::Local>,
279	/// When the movement was last updated.
280	pub updated_at: DateTime<chrono::Local>,
281	/// The action responsible for registering the movement.
282	#[serde(default, skip_serializing_if = "Option::is_none")]
283	pub completed_at: Option<DateTime<chrono::Local>>,
284}
285
286impl From<bark::movement::MovementTimestamp> for MovementTimestamp {
287	fn from(t: bark::movement::MovementTimestamp) -> Self {
288		MovementTimestamp {
289			created_at: t.created_at,
290			updated_at: t.updated_at,
291			completed_at: t.completed_at,
292		}
293	}
294}