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 Pending,
19 Successful,
21 Failed,
24 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#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
42#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
43pub struct Movement {
44 #[cfg_attr(feature = "utoipa", schema(value_type = u32))]
46 pub id: MovementId,
47 pub status: MovementStatus,
49 pub subsystem: MovementSubsystem,
52 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub metadata: Option<serde_json::Map<String, serde_json::Value>>,
56 #[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 #[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 #[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 pub sent_to: Vec<MovementDestination>,
75 pub received_on: Vec<MovementDestination>,
78 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
81 pub input_vtxos: Vec<VtxoId>,
82 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
85 pub output_vtxos: Vec<VtxoId>,
86 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
91 pub exited_vtxos: Vec<VtxoId>,
92 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#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
119#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
120pub struct MovementDestination {
121 pub destination: PaymentMethod,
123 #[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#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
142#[serde(tag = "type", content = "value", rename_all = "kebab-case")]
143pub enum PaymentMethod {
144 Ark(String),
146 Bitcoin(String),
148 OutputScript(String),
151 Invoice(String),
153 Offer(String),
155 LightningAddress(String),
157 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#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
248#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
249pub struct MovementSubsystem {
250 pub name: String,
252 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#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
267#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
268pub struct MovementTimestamp {
269 pub created_at: DateTime<chrono::Local>,
271 pub updated_at: DateTime<chrono::Local>,
273 #[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}