use std::collections::HashMap;
use std::str::FromStr;
use anyhow::anyhow;
use bitcoin::{Amount, ScriptBuf, SignedAmount};
use chrono::DateTime;
use ark::VtxoId;
use ark::lightning::{Invoice, Offer};
use bark::lnurllib::lightning_address::LightningAddress;
use bark::movement::MovementId;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum MovementStatus {
Pending,
Successful,
Failed,
Canceled,
}
impl From<bark::movement::MovementStatus> for MovementStatus {
fn from(v: bark::movement::MovementStatus) -> Self {
match v {
bark::movement::MovementStatus::Pending => Self::Pending,
bark::movement::MovementStatus::Successful => Self::Successful,
bark::movement::MovementStatus::Failed => Self::Failed,
bark::movement::MovementStatus::Canceled => Self::Canceled,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct Movement {
#[cfg_attr(feature = "utoipa", schema(value_type = u32))]
pub id: MovementId,
pub status: MovementStatus,
pub subsystem: MovementSubsystem,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, serde_json::Value>>,
#[serde(rename="intended_balance_sat", with="bitcoin::amount::serde::as_sat")]
#[cfg_attr(feature = "utoipa", schema(value_type = i64))]
pub intended_balance: SignedAmount,
#[serde(rename="effective_balance_sat", with="bitcoin::amount::serde::as_sat")]
#[cfg_attr(feature = "utoipa", schema(value_type = i64))]
pub effective_balance: SignedAmount,
#[serde(rename="offchain_fee_sat", with="bitcoin::amount::serde::as_sat")]
#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
pub offchain_fee: Amount,
pub sent_to: Vec<MovementDestination>,
pub received_on: Vec<MovementDestination>,
#[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
pub input_vtxos: Vec<VtxoId>,
#[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
pub output_vtxos: Vec<VtxoId>,
#[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>))]
pub exited_vtxos: Vec<VtxoId>,
pub time: MovementTimestamp,
}
impl From<bark::movement::Movement> for Movement {
fn from(m: bark::movement::Movement) -> Self {
Movement {
id: m.id,
status: m.status.into(),
subsystem: MovementSubsystem::from(m.subsystem),
metadata: if m.metadata.is_empty() { None } else { Some(m.metadata) },
intended_balance: m.intended_balance,
effective_balance: m.effective_balance,
offchain_fee: m.offchain_fee,
sent_to: m.sent_to.into_iter().map(MovementDestination::from).collect(),
received_on: m.received_on.into_iter().map(MovementDestination::from).collect(),
input_vtxos: m.input_vtxos,
output_vtxos: m.output_vtxos,
exited_vtxos: m.exited_vtxos,
time: MovementTimestamp::from(m.time),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct MovementDestination {
pub destination: PaymentMethod,
#[serde(rename="amount_sat", with="bitcoin::amount::serde::as_sat")]
#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
pub amount: Amount,
}
impl From<bark::movement::MovementDestination> for MovementDestination {
fn from(d: bark::movement::MovementDestination) -> Self {
MovementDestination {
destination: PaymentMethod::from(d.destination),
amount: d.amount,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(tag = "type", content = "value", rename_all = "kebab-case")]
pub enum PaymentMethod {
Ark(String),
Bitcoin(String),
OutputScript(String),
Invoice(String),
Offer(String),
LightningAddress(String),
Custom(String),
}
#[cfg(feature = "utoipa")]
impl utoipa::PartialSchema for PaymentMethod {
fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
use utoipa::openapi::schema;
schema::ObjectBuilder::new()
.title(Some("PaymentMethod"))
.description(Some("A payment method with a type discriminator and string value"))
.property(
"type",
schema::ObjectBuilder::new()
.schema_type(schema::SchemaType::Type(schema::Type::String))
.enum_values(Some([
"ark",
"bitcoin",
"output-script",
"invoice",
"offer",
"lightning-address",
"custom",
]))
.description(Some("The type of payment method"))
)
.required("type")
.property(
"value",
schema::ObjectBuilder::new()
.schema_type(schema::SchemaType::Type(schema::Type::String))
.description(Some("The payment method value (address, invoice, etc.)"))
)
.required("value")
.into()
}
}
#[cfg(feature = "utoipa")]
impl utoipa::ToSchema for PaymentMethod {
fn name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("PaymentMethod")
}
}
impl From<bark::movement::PaymentMethod> for PaymentMethod {
fn from(p: bark::movement::PaymentMethod) -> Self {
match p {
bark::movement::PaymentMethod::Ark(a) => Self::Ark(a.to_string()),
bark::movement::PaymentMethod::Bitcoin(b) => Self::Bitcoin(b.assume_checked().to_string()),
bark::movement::PaymentMethod::OutputScript(s) => Self::OutputScript(s.to_hex_string()),
bark::movement::PaymentMethod::Invoice(i) => Self::Invoice(i.to_string()),
bark::movement::PaymentMethod::Offer(o) => Self::Offer(o.to_string()),
bark::movement::PaymentMethod::LightningAddress(l) => Self::LightningAddress(l.to_string()),
bark::movement::PaymentMethod::Custom(c) => Self::Custom(c),
}
}
}
impl TryFrom<PaymentMethod> for bark::movement::PaymentMethod {
type Error = anyhow::Error;
fn try_from(p: PaymentMethod) -> Result<Self, Self::Error> {
match p {
PaymentMethod::Ark(a) => Ok(bark::movement::PaymentMethod::Ark(
ark::Address::from_str(&a)?,
)),
PaymentMethod::Bitcoin(b) => Ok(bark::movement::PaymentMethod::Bitcoin(
bitcoin::Address::from_str(&b)?,
)),
PaymentMethod::OutputScript(s) => Ok(bark::movement::PaymentMethod::OutputScript(
ScriptBuf::from_hex(&s)?,
)),
PaymentMethod::Invoice(i) => Ok(bark::movement::PaymentMethod::Invoice(
Invoice::from_str(&i)?,
)),
PaymentMethod::Offer(o) => Ok(bark::movement::PaymentMethod::Offer(
Offer::from_str(&o).map_err(|e| anyhow!("Failed to parse offer: {:?}", e))?,
)),
PaymentMethod::LightningAddress(l) => Ok(bark::movement::PaymentMethod::LightningAddress(
LightningAddress::from_str(&l)?,
)),
PaymentMethod::Custom(c) => Ok(bark::movement::PaymentMethod::Custom(c)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct MovementSubsystem {
pub name: String,
pub kind: String,
}
impl From<bark::movement::MovementSubsystem> for MovementSubsystem {
fn from(s: bark::movement::MovementSubsystem) -> Self {
MovementSubsystem {
name: s.name,
kind: s.kind,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct MovementTimestamp {
pub created_at: DateTime<chrono::Local>,
pub updated_at: DateTime<chrono::Local>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub completed_at: Option<DateTime<chrono::Local>>,
}
impl From<bark::movement::MovementTimestamp> for MovementTimestamp {
fn from(t: bark::movement::MovementTimestamp) -> Self {
MovementTimestamp {
created_at: t.created_at,
updated_at: t.updated_at,
completed_at: t.completed_at,
}
}
}