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 Pending,
20 Successful,
22 Failed,
25 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#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
43#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
44pub struct Movement {
45 #[cfg_attr(feature = "utoipa", schema(value_type = u32))]
47 pub id: MovementId,
48 pub status: MovementStatus,
50 pub subsystem: MovementSubsystem,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub metadata: Option<serde_json::Map<String, serde_json::Value>>,
57 #[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 #[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 #[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 pub sent_to: Vec<MovementDestination>,
76 pub received_on: Vec<MovementDestination>,
79 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
82 pub input_vtxos: Vec<VtxoId>,
83 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
86 pub output_vtxos: Vec<VtxoId>,
87 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
92 pub exited_vtxos: Vec<VtxoId>,
93 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#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
120#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
121pub struct MovementDestination {
122 pub destination: PaymentMethod,
124 #[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#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
143#[serde(tag = "type", content = "value", rename_all = "kebab-case")]
144pub enum PaymentMethod {
145 Ark(String),
147 Bitcoin(String),
149 OutputScript(String),
152 Invoice(String),
154 Offer(String),
156 LightningAddress(String),
158 Lnurl(String),
160 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#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
256#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
257pub struct MovementSubsystem {
258 pub name: String,
260 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#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
275#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
276pub struct MovementTimestamp {
277 pub created_at: DateTime<chrono::Local>,
279 pub updated_at: DateTime<chrono::Local>,
281 #[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}