1
2
3pub mod error;
4pub mod manager;
5pub mod update;
6mod payment_method;
7
8use crate::subsystem::{LightningMovement, Subsystem};
9
10pub use self::payment_method::PaymentMethod;
11
12use std::collections::HashMap;
13use std::fmt;
14use std::str::FromStr;
15
16use bitcoin::{Amount, SignedAmount};
17use chrono::DateTime;
18use lightning::offers::offer::Offer;
19use lnurllib::lightning_address::LightningAddress;
20use serde::{Deserialize, Serialize};
21
22use ark::VtxoId;
23use ark::lightning::{Invoice, PaymentHash};
24
25const MOVEMENT_PENDING: &'static str = "pending";
26const MOVEMENT_SUCCESSFUL: &'static str = "successful";
27const MOVEMENT_FAILED: &'static str = "failed";
28const MOVEMENT_CANCELED: &'static str = "canceled";
29
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32pub struct Movement {
33 pub id: MovementId,
35 pub status: MovementStatus,
37 pub subsystem: MovementSubsystem,
40 pub metadata: HashMap<String, serde_json::Value>,
43 #[serde(with = "bitcoin::amount::serde::as_sat")]
46 pub intended_balance: SignedAmount,
47 #[serde(with = "bitcoin::amount::serde::as_sat")]
51 pub effective_balance: SignedAmount,
52 #[serde(with = "bitcoin::amount::serde::as_sat")]
56 pub offchain_fee: Amount,
57 pub sent_to: Vec<MovementDestination>,
59 pub received_on: Vec<MovementDestination>,
62 pub input_vtxos: Vec<VtxoId>,
65 pub output_vtxos: Vec<VtxoId>,
68 pub exited_vtxos: Vec<VtxoId>,
73 pub time: MovementTimestamp,
75}
76
77impl Movement {
78 pub fn new(
79 id: MovementId,
80 status: MovementStatus,
81 subsystem: &MovementSubsystem,
82 time: DateTime<chrono::Local>,
83 ) -> Self {
84 Self {
85 id,
86 status,
87 subsystem: subsystem.clone(),
88 time: MovementTimestamp {
89 created_at: time,
90 updated_at: time,
91 completed_at: None,
92 },
93 metadata: HashMap::new(),
94 intended_balance: SignedAmount::ZERO,
95 effective_balance: SignedAmount::ZERO,
96 offchain_fee: Amount::ZERO,
97 sent_to: vec![],
98 received_on: vec![],
99 input_vtxos: vec![],
100 output_vtxos: vec![],
101 exited_vtxos: vec![],
102 }
103 }
104
105 pub fn received_on(&self, payment_method: &PaymentMethod) -> bool {
107 self.received_on.iter().any(|d| d.destination == *payment_method)
108 }
109
110 pub fn sent_to(&self, payment_method: &PaymentMethod) -> bool {
112 self.sent_to.iter().any(|d| d.destination == *payment_method)
113 }
114
115 pub fn lightning_invoice(&self) -> Option<&Invoice> {
119 for dest in &self.received_on {
120 if let PaymentMethod::Invoice(ref i) = dest.destination {
121 return Some(i);
122 }
123 }
124
125 for dest in &self.sent_to {
126 if let PaymentMethod::Invoice(ref i) = dest.destination {
127 return Some(i);
128 }
129 }
130
131 None
132 }
133
134 pub fn lightning_offer(&self) -> Option<&Offer> {
138 for dest in &self.received_on {
139 if let PaymentMethod::Offer(ref o) = dest.destination {
140 return Some(o);
141 }
142 }
143
144 for dest in &self.sent_to {
145 if let PaymentMethod::Offer(ref o) = dest.destination {
146 return Some(o);
147 }
148 }
149
150 None
151 }
152
153 pub fn lightning_payment_hash(&self) -> Option<PaymentHash> {
157 LightningMovement::get_payment_hash(&self.metadata)
158 .or_else(|| self.lightning_invoice().map(|i| i.payment_hash()))
159 }
160}
161
162#[derive(Clone, Copy, Eq, Hash, PartialEq, Deserialize, Serialize, Ord, PartialOrd)]
164pub struct MovementId(pub u32);
165
166impl MovementId {
167 pub fn new(id: u32) -> Self {
168 Self(id)
169 }
170
171 pub fn to_bytes(&self) -> [u8; 4] {
172 self.0.to_be_bytes()
173 }
174}
175
176impl fmt::Display for MovementId {
177 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
178 fmt::Display::fmt(&self.0, f)
179 }
180}
181
182impl fmt::Debug for MovementId {
183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184 fmt::Display::fmt(&self, f)
185 }
186}
187
188#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
193pub enum MovementStatus {
194 Pending,
196 Successful,
199 Failed,
202 Canceled,
205}
206
207impl MovementStatus {
208 pub fn as_str(&self) -> &'static str {
213 match self {
214 Self::Pending => MOVEMENT_PENDING,
215 Self::Successful => MOVEMENT_SUCCESSFUL,
216 Self::Failed => MOVEMENT_FAILED,
217 Self::Canceled => MOVEMENT_CANCELED,
218 }
219 }
220}
221
222impl fmt::Display for MovementStatus {
223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 f.write_str(self.as_str())
225 }
226}
227
228impl fmt::Debug for MovementStatus {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 fmt::Display::fmt(&self, f)
231 }
232}
233
234impl FromStr for MovementStatus {
235 type Err = anyhow::Error;
236
237 fn from_str(s: &str) -> Result<Self, Self::Err> {
239 match s {
240 MOVEMENT_PENDING => Ok(MovementStatus::Pending),
241 MOVEMENT_SUCCESSFUL => Ok(MovementStatus::Successful),
242 MOVEMENT_FAILED => Ok(MovementStatus::Failed),
243 MOVEMENT_CANCELED => Ok(MovementStatus::Canceled),
244 _ => bail!("Invalid MovementStatus: {}", s),
245 }
246 }
247}
248
249impl Serialize for MovementStatus {
250 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
251 where
252 S: serde::Serializer,
253 {
254 serializer.serialize_str(self.as_str())
255 }
256}
257
258impl<'de> Deserialize<'de> for MovementStatus {
259 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
260 where
261 D: serde::Deserializer<'de>,
262 {
263 let s = String::deserialize(deserializer)?;
264 MovementStatus::from_str(&s).map_err(serde::de::Error::custom)
265 }
266}
267
268#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
271pub struct MovementDestination {
272 pub destination: PaymentMethod,
274 #[serde(with = "bitcoin::amount::serde::as_sat")]
276 pub amount: Amount,
277}
278
279impl MovementDestination {
280 pub fn new(payment_method: PaymentMethod, amount: Amount) -> Self {
281 Self { destination: payment_method, amount }
282 }
283
284 pub fn ark(address: ark::Address, amount: Amount) -> Self {
285 Self::new(address.into(), amount)
286 }
287
288 pub fn bitcoin(address: bitcoin::Address, amount: Amount) -> Self {
289 Self::new(address.into(), amount)
290 }
291
292 pub fn invoice(invoice: Invoice, amount: Amount) -> Self {
293 Self::new(invoice.into(), amount)
294 }
295
296 pub fn offer(offer: Offer, amount: Amount) -> Self {
297 Self::new(offer.into(), amount)
298 }
299
300 pub fn lightning_address(address: LightningAddress, amount: Amount) -> Self {
301 Self::new(address.into(), amount)
302 }
303
304 pub fn custom(destination: String, amount: Amount) -> Self {
305 Self::new(PaymentMethod::Custom(destination), amount)
306 }
307}
308
309#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
312pub struct MovementSubsystem {
313 pub name: String,
315 pub kind: String,
317}
318
319impl MovementSubsystem {
320 pub fn is_subsystem(&self, subsystem: Subsystem) -> bool {
322 self.name.as_str() == subsystem.as_name()
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
328pub struct MovementTimestamp {
329 pub created_at: DateTime<chrono::Local>,
331 pub updated_at: DateTime<chrono::Local>,
333 pub completed_at: Option<DateTime<chrono::Local>>,
335}