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