use std::pin::Pin;
use async_trait::async_trait;
use futures::Stream;
use lightning_invoice::{Bolt11Invoice, ParseOrSemanticError};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuoteState};
use crate::{mint, Amount};
#[derive(Debug, Error)]
pub enum Error {
#[error("Invoice already paid")]
InvoiceAlreadyPaid,
#[error("Invoice pay is pending")]
InvoicePaymentPending,
#[error(transparent)]
Lightning(Box<dyn std::error::Error + Send + Sync>),
#[error(transparent)]
Serde(#[from] serde_json::Error),
#[error(transparent)]
Anyhow(#[from] anyhow::Error),
#[error(transparent)]
Parse(#[from] ParseOrSemanticError),
#[error("Cannot convert units")]
CannotConvertUnits,
}
#[async_trait]
pub trait MintLightning {
type Err: Into<Error> + From<Error>;
fn get_settings(&self) -> Settings;
async fn create_invoice(
&self,
amount: Amount,
unit: &CurrencyUnit,
description: String,
unix_expiry: u64,
) -> Result<CreateInvoiceResponse, Self::Err>;
async fn get_payment_quote(
&self,
melt_quote_request: &MeltQuoteBolt11Request,
) -> Result<PaymentQuoteResponse, Self::Err>;
async fn pay_invoice(
&self,
melt_quote: mint::MeltQuote,
partial_amount: Option<Amount>,
max_fee_amount: Option<Amount>,
) -> Result<PayInvoiceResponse, Self::Err>;
async fn wait_any_invoice(
&self,
) -> Result<Pin<Box<dyn Stream<Item = String> + Send>>, Self::Err>;
async fn check_invoice_status(
&self,
request_lookup_id: &str,
) -> Result<MintQuoteState, Self::Err>;
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct CreateInvoiceResponse {
pub request_lookup_id: String,
pub request: Bolt11Invoice,
pub expiry: Option<u64>,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct PayInvoiceResponse {
pub payment_hash: String,
pub payment_preimage: Option<String>,
pub status: MeltQuoteState,
pub total_spent: Amount,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct PaymentQuoteResponse {
pub request_lookup_id: String,
pub amount: Amount,
pub fee: Amount,
pub state: MeltQuoteState,
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct Settings {
pub mpp: bool,
pub mint_settings: MintMeltSettings,
pub melt_settings: MintMeltSettings,
pub unit: CurrencyUnit,
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct MintMeltSettings {
pub min_amount: Amount,
pub max_amount: Amount,
pub enabled: bool,
}
impl Default for MintMeltSettings {
fn default() -> Self {
Self {
min_amount: Amount::from(1),
max_amount: Amount::from(500000),
enabled: true,
}
}
}
pub const MSAT_IN_SAT: u64 = 1000;
pub fn to_unit<T>(
amount: T,
current_unit: &CurrencyUnit,
target_unit: &CurrencyUnit,
) -> Result<Amount, Error>
where
T: Into<u64>,
{
let amount = amount.into();
match (current_unit, target_unit) {
(CurrencyUnit::Sat, CurrencyUnit::Sat) => Ok(amount.into()),
(CurrencyUnit::Msat, CurrencyUnit::Msat) => Ok(amount.into()),
(CurrencyUnit::Sat, CurrencyUnit::Msat) => Ok((amount * MSAT_IN_SAT).into()),
(CurrencyUnit::Msat, CurrencyUnit::Sat) => Ok((amount / MSAT_IN_SAT).into()),
(CurrencyUnit::Usd, CurrencyUnit::Usd) => Ok(amount.into()),
(CurrencyUnit::Eur, CurrencyUnit::Eur) => Ok(amount.into()),
_ => Err(Error::CannotConvertUnits),
}
}