pub mod error;
pub mod manager;
pub mod update;
mod payment_method;
use crate::subsystem::{LightningMovement, Subsystem};
pub use self::payment_method::PaymentMethod;
use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
use bitcoin::{Amount, SignedAmount};
use chrono::DateTime;
use lightning::offers::offer::Offer;
use lnurllib::lightning_address::LightningAddress;
use serde::{Deserialize, Serialize};
use ark::VtxoId;
use ark::lightning::{Invoice, PaymentHash};
const MOVEMENT_PENDING: &'static str = "pending";
const MOVEMENT_SUCCESSFUL: &'static str = "successful";
const MOVEMENT_FAILED: &'static str = "failed";
const MOVEMENT_CANCELED: &'static str = "canceled";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Movement {
pub id: MovementId,
pub status: MovementStatus,
pub subsystem: MovementSubsystem,
pub metadata: HashMap<String, serde_json::Value>,
#[serde(with = "bitcoin::amount::serde::as_sat")]
pub intended_balance: SignedAmount,
#[serde(with = "bitcoin::amount::serde::as_sat")]
pub effective_balance: SignedAmount,
#[serde(with = "bitcoin::amount::serde::as_sat")]
pub offchain_fee: Amount,
pub sent_to: Vec<MovementDestination>,
pub received_on: Vec<MovementDestination>,
pub input_vtxos: Vec<VtxoId>,
pub output_vtxos: Vec<VtxoId>,
pub exited_vtxos: Vec<VtxoId>,
pub time: MovementTimestamp,
}
impl Movement {
pub fn new(
id: MovementId,
status: MovementStatus,
subsystem: &MovementSubsystem,
time: DateTime<chrono::Local>,
) -> Self {
Self {
id,
status,
subsystem: subsystem.clone(),
time: MovementTimestamp {
created_at: time,
updated_at: time,
completed_at: None,
},
metadata: HashMap::new(),
intended_balance: SignedAmount::ZERO,
effective_balance: SignedAmount::ZERO,
offchain_fee: Amount::ZERO,
sent_to: vec![],
received_on: vec![],
input_vtxos: vec![],
output_vtxos: vec![],
exited_vtxos: vec![],
}
}
pub fn received_on(&self, payment_method: &PaymentMethod) -> bool {
self.received_on.iter().any(|d| d.destination == *payment_method)
}
pub fn sent_to(&self, payment_method: &PaymentMethod) -> bool {
self.sent_to.iter().any(|d| d.destination == *payment_method)
}
pub fn lightning_invoice(&self) -> Option<&Invoice> {
for dest in &self.received_on {
if let PaymentMethod::Invoice(ref i) = dest.destination {
return Some(i);
}
}
for dest in &self.sent_to {
if let PaymentMethod::Invoice(ref i) = dest.destination {
return Some(i);
}
}
None
}
pub fn lightning_offer(&self) -> Option<&Offer> {
for dest in &self.received_on {
if let PaymentMethod::Offer(ref o) = dest.destination {
return Some(o);
}
}
for dest in &self.sent_to {
if let PaymentMethod::Offer(ref o) = dest.destination {
return Some(o);
}
}
None
}
pub fn lightning_payment_hash(&self) -> Option<PaymentHash> {
LightningMovement::get_payment_hash(&self.metadata)
.or_else(|| self.lightning_invoice().map(|i| i.payment_hash()))
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq, Deserialize, Serialize, Ord, PartialOrd)]
pub struct MovementId(pub u32);
impl MovementId {
pub fn new(id: u32) -> Self {
Self(id)
}
pub fn to_bytes(&self) -> [u8; 4] {
self.0.to_be_bytes()
}
}
impl fmt::Display for MovementId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl fmt::Debug for MovementId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self, f)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum MovementStatus {
Pending,
Successful,
Failed,
Canceled,
}
impl MovementStatus {
pub fn as_str(&self) -> &'static str {
match self {
Self::Pending => MOVEMENT_PENDING,
Self::Successful => MOVEMENT_SUCCESSFUL,
Self::Failed => MOVEMENT_FAILED,
Self::Canceled => MOVEMENT_CANCELED,
}
}
}
impl fmt::Display for MovementStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl fmt::Debug for MovementStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self, f)
}
}
impl FromStr for MovementStatus {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
MOVEMENT_PENDING => Ok(MovementStatus::Pending),
MOVEMENT_SUCCESSFUL => Ok(MovementStatus::Successful),
MOVEMENT_FAILED => Ok(MovementStatus::Failed),
MOVEMENT_CANCELED => Ok(MovementStatus::Canceled),
_ => bail!("Invalid MovementStatus: {}", s),
}
}
}
impl Serialize for MovementStatus {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for MovementStatus {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
MovementStatus::from_str(&s).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MovementDestination {
pub destination: PaymentMethod,
#[serde(with = "bitcoin::amount::serde::as_sat")]
pub amount: Amount,
}
impl MovementDestination {
pub fn new(payment_method: PaymentMethod, amount: Amount) -> Self {
Self { destination: payment_method, amount }
}
pub fn ark(address: ark::Address, amount: Amount) -> Self {
Self::new(address.into(), amount)
}
pub fn bitcoin(address: bitcoin::Address, amount: Amount) -> Self {
Self::new(address.into(), amount)
}
pub fn invoice(invoice: Invoice, amount: Amount) -> Self {
Self::new(invoice.into(), amount)
}
pub fn offer(offer: Offer, amount: Amount) -> Self {
Self::new(offer.into(), amount)
}
pub fn lightning_address(address: LightningAddress, amount: Amount) -> Self {
Self::new(address.into(), amount)
}
pub fn custom(destination: String, amount: Amount) -> Self {
Self::new(PaymentMethod::Custom(destination), amount)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct MovementSubsystem {
pub name: String,
pub kind: String,
}
impl MovementSubsystem {
pub fn is_subsystem(&self, subsystem: Subsystem) -> bool {
self.name.as_str() == subsystem.as_name()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct MovementTimestamp {
pub created_at: DateTime<chrono::Local>,
pub updated_at: DateTime<chrono::Local>,
pub completed_at: Option<DateTime<chrono::Local>>,
}