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