use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
use bitcoin::bip32::DerivationPath;
use cashu::quote_id::QuoteId;
use cashu::util::unix_time;
use cashu::{
Bolt11Invoice, MeltOptions, MeltQuoteBolt11Response, MintQuoteBolt11Response,
MintQuoteBolt12Response, PaymentMethod, Proofs, State,
};
use lightning::offers::offer::Offer;
use serde::{Deserialize, Serialize};
use tracing::instrument;
use uuid::Uuid;
use crate::common::IssuerVersion;
use crate::mint_quote::MintQuoteResponse;
use crate::nuts::{MeltQuoteState, MintQuoteState};
use crate::payment::PaymentIdentifier;
use crate::{Amount, CurrencyUnit, Error, Id, KeySetInfo, PublicKey};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OperationKind {
Swap,
Mint,
Melt,
BatchMint,
}
#[derive(Debug)]
pub struct ProofsWithState {
proofs: Proofs,
pub state: State,
}
impl Deref for ProofsWithState {
type Target = Proofs;
fn deref(&self) -> &Self::Target {
&self.proofs
}
}
impl ProofsWithState {
pub fn new(proofs: Proofs, current_state: State) -> Self {
Self {
proofs,
state: current_state,
}
}
}
impl fmt::Display for OperationKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OperationKind::Swap => write!(f, "swap"),
OperationKind::Mint => write!(f, "mint"),
OperationKind::Melt => write!(f, "melt"),
OperationKind::BatchMint => write!(f, "batch_mint"),
}
}
}
impl FromStr for OperationKind {
type Err = Error;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let value = value.to_lowercase();
match value.as_str() {
"swap" => Ok(OperationKind::Swap),
"mint" => Ok(OperationKind::Mint),
"melt" => Ok(OperationKind::Melt),
"batch_mint" => Ok(OperationKind::BatchMint),
_ => Err(Error::Custom(format!("Invalid operation kind: {value}"))),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SwapSagaState {
SetupComplete,
Signed,
}
impl fmt::Display for SwapSagaState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SwapSagaState::SetupComplete => write!(f, "setup_complete"),
SwapSagaState::Signed => write!(f, "signed"),
}
}
}
impl FromStr for SwapSagaState {
type Err = Error;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let value = value.to_lowercase();
match value.as_str() {
"setup_complete" => Ok(SwapSagaState::SetupComplete),
"signed" => Ok(SwapSagaState::Signed),
_ => Err(Error::Custom(format!("Invalid swap saga state: {value}"))),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MeltSagaState {
SetupComplete,
PaymentAttempted,
Finalizing,
}
impl fmt::Display for MeltSagaState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MeltSagaState::SetupComplete => write!(f, "setup_complete"),
MeltSagaState::PaymentAttempted => write!(f, "payment_attempted"),
MeltSagaState::Finalizing => write!(f, "finalizing"),
}
}
}
impl FromStr for MeltSagaState {
type Err = Error;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let value = value.to_lowercase();
match value.as_str() {
"setup_complete" => Ok(MeltSagaState::SetupComplete),
"payment_attempted" => Ok(MeltSagaState::PaymentAttempted),
"finalizing" => Ok(MeltSagaState::Finalizing),
_ => Err(Error::Custom(format!("Invalid melt saga state: {}", value))),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum SagaStateEnum {
Swap(SwapSagaState),
Melt(MeltSagaState),
}
impl SagaStateEnum {
pub fn new(operation_kind: OperationKind, s: &str) -> Result<Self, Error> {
match operation_kind {
OperationKind::Swap => Ok(SagaStateEnum::Swap(SwapSagaState::from_str(s)?)),
OperationKind::Melt => Ok(SagaStateEnum::Melt(MeltSagaState::from_str(s)?)),
OperationKind::Mint | OperationKind::BatchMint => {
Err(Error::Custom("Mint saga not implemented yet".to_string()))
}
}
}
pub fn state(&self) -> &str {
match self {
SagaStateEnum::Swap(state) => match state {
SwapSagaState::SetupComplete => "setup_complete",
SwapSagaState::Signed => "signed",
},
SagaStateEnum::Melt(state) => match state {
MeltSagaState::SetupComplete => "setup_complete",
MeltSagaState::PaymentAttempted => "payment_attempted",
MeltSagaState::Finalizing => "finalizing",
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Saga {
pub operation_id: Uuid,
pub operation_kind: OperationKind,
pub state: SagaStateEnum,
pub quote_id: Option<String>,
pub created_at: u64,
pub updated_at: u64,
}
impl Saga {
pub fn new_swap(operation_id: Uuid, state: SwapSagaState) -> Self {
let now = unix_time();
Self {
operation_id,
operation_kind: OperationKind::Swap,
state: SagaStateEnum::Swap(state),
quote_id: None,
created_at: now,
updated_at: now,
}
}
pub fn update_swap_state(&mut self, new_state: SwapSagaState) {
self.state = SagaStateEnum::Swap(new_state);
self.updated_at = unix_time();
}
pub fn new_melt(operation_id: Uuid, state: MeltSagaState, quote_id: String) -> Self {
let now = unix_time();
Self {
operation_id,
operation_kind: OperationKind::Melt,
state: SagaStateEnum::Melt(state),
quote_id: Some(quote_id),
created_at: now,
updated_at: now,
}
}
pub fn update_melt_state(&mut self, new_state: MeltSagaState) {
self.state = SagaStateEnum::Melt(new_state);
self.updated_at = unix_time();
}
}
#[derive(Debug)]
pub struct Operation {
id: Uuid,
kind: OperationKind,
total_issued: Amount,
total_redeemed: Amount,
fee_collected: Amount,
complete_at: Option<u64>,
payment_amount: Option<Amount>,
payment_fee: Option<Amount>,
payment_method: Option<PaymentMethod>,
}
impl Operation {
pub fn new(
id: Uuid,
kind: OperationKind,
total_issued: Amount,
total_redeemed: Amount,
fee_collected: Amount,
complete_at: Option<u64>,
payment_method: Option<PaymentMethod>,
) -> Self {
Self {
id,
kind,
total_issued,
total_redeemed,
fee_collected,
complete_at,
payment_amount: None,
payment_fee: None,
payment_method,
}
}
pub fn new_mint(total_issued: Amount, payment_method: PaymentMethod) -> Self {
Self {
id: Uuid::new_v4(),
kind: OperationKind::Mint,
total_issued,
total_redeemed: Amount::ZERO,
fee_collected: Amount::ZERO,
complete_at: None,
payment_amount: None,
payment_fee: None,
payment_method: Some(payment_method),
}
}
pub fn new_batch_mint(total_issued: Amount, payment_method: PaymentMethod) -> Self {
Self {
id: Uuid::new_v4(),
kind: OperationKind::BatchMint,
total_issued,
total_redeemed: Amount::ZERO,
fee_collected: Amount::ZERO,
complete_at: None,
payment_amount: None,
payment_fee: None,
payment_method: Some(payment_method),
}
}
pub fn new_melt(
total_redeemed: Amount,
fee_collected: Amount,
payment_method: PaymentMethod,
) -> Self {
Self {
id: Uuid::new_v4(),
kind: OperationKind::Melt,
total_issued: Amount::ZERO,
total_redeemed,
fee_collected,
complete_at: None,
payment_amount: None,
payment_fee: None,
payment_method: Some(payment_method),
}
}
pub fn new_swap(total_issued: Amount, total_redeemed: Amount, fee_collected: Amount) -> Self {
Self {
id: Uuid::new_v4(),
kind: OperationKind::Swap,
total_issued,
total_redeemed,
fee_collected,
complete_at: None,
payment_amount: None,
payment_fee: None,
payment_method: None,
}
}
pub fn id(&self) -> &Uuid {
&self.id
}
pub fn kind(&self) -> OperationKind {
self.kind
}
pub fn total_issued(&self) -> Amount {
self.total_issued
}
pub fn total_redeemed(&self) -> Amount {
self.total_redeemed
}
pub fn fee_collected(&self) -> Amount {
self.fee_collected
}
pub fn completed_at(&self) -> &Option<u64> {
&self.complete_at
}
pub fn add_change(&mut self, change: Amount) {
self.total_issued = change;
}
pub fn payment_amount(&self) -> Option<Amount> {
self.payment_amount
}
pub fn payment_fee(&self) -> Option<Amount> {
self.payment_fee
}
pub fn set_payment_details(&mut self, payment_amount: Amount, payment_fee: Amount) {
self.payment_amount = Some(payment_amount);
self.payment_fee = Some(payment_fee);
}
pub fn payment_method(&self) -> Option<PaymentMethod> {
self.payment_method.clone()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct MintQuoteChange {
pub payments: Option<Vec<IncomingPayment>>,
pub issuances: Option<Vec<Amount>>,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct MintQuote {
pub id: QuoteId,
pub amount: Option<Amount<CurrencyUnit>>,
pub unit: CurrencyUnit,
pub request: String,
pub expiry: u64,
pub request_lookup_id: PaymentIdentifier,
pub pubkey: Option<PublicKey>,
pub created_time: u64,
amount_paid: Amount<CurrencyUnit>,
amount_issued: Amount<CurrencyUnit>,
pub payments: Vec<IncomingPayment>,
pub payment_method: PaymentMethod,
pub issuance: Vec<Issuance>,
pub extra_json: Option<serde_json::Value>,
changes: Option<MintQuoteChange>,
}
impl MintQuote {
#[allow(clippy::too_many_arguments)]
pub fn new(
id: Option<QuoteId>,
request: String,
unit: CurrencyUnit,
amount: Option<Amount<CurrencyUnit>>,
expiry: u64,
request_lookup_id: PaymentIdentifier,
pubkey: Option<PublicKey>,
amount_paid: Amount<CurrencyUnit>,
amount_issued: Amount<CurrencyUnit>,
payment_method: PaymentMethod,
created_time: u64,
payments: Vec<IncomingPayment>,
issuance: Vec<Issuance>,
extra_json: Option<serde_json::Value>,
) -> Self {
let id = id.unwrap_or_else(QuoteId::new_uuid);
Self {
id,
amount,
unit: unit.clone(),
request,
expiry,
request_lookup_id,
pubkey,
created_time,
amount_paid,
amount_issued,
payment_method,
payments,
issuance,
extra_json,
changes: None,
}
}
#[instrument(skip(self))]
pub fn increment_amount_paid(
&mut self,
additional_amount: Amount<CurrencyUnit>,
) -> Result<Amount, crate::Error> {
self.amount_paid = self
.amount_paid
.checked_add(&additional_amount)
.map_err(|_| crate::Error::AmountOverflow)?;
Ok(Amount::from(self.amount_paid.value()))
}
#[instrument(skip(self))]
pub fn amount_paid(&self) -> Amount<CurrencyUnit> {
self.amount_paid.clone()
}
#[instrument(skip(self))]
pub fn add_issuance(
&mut self,
additional_amount: Amount<CurrencyUnit>,
) -> Result<Amount<CurrencyUnit>, crate::Error> {
let new_amount_issued = self
.amount_issued
.checked_add(&additional_amount)
.map_err(|_| crate::Error::AmountOverflow)?;
if new_amount_issued > self.amount_paid {
return Err(crate::Error::OverIssue);
}
self.changes
.get_or_insert_default()
.issuances
.get_or_insert_default()
.push(additional_amount.into());
self.amount_issued = new_amount_issued;
Ok(self.amount_issued.clone())
}
#[instrument(skip(self))]
pub fn amount_issued(&self) -> Amount<CurrencyUnit> {
self.amount_issued.clone()
}
#[instrument(skip(self))]
pub fn state(&self) -> MintQuoteState {
self.compute_quote_state()
}
pub fn payment_ids(&self) -> Vec<&String> {
self.payments.iter().map(|a| &a.payment_id).collect()
}
pub fn amount_mintable(&self) -> Amount<CurrencyUnit> {
self.amount_paid
.checked_sub(&self.amount_issued)
.unwrap_or_else(|_| Amount::new(0, self.unit.clone()))
}
pub fn take_changes(&mut self) -> Option<MintQuoteChange> {
self.changes.take()
}
#[instrument(skip(self))]
pub fn add_payment(
&mut self,
amount: Amount<CurrencyUnit>,
payment_id: String,
time: Option<u64>,
) -> Result<(), crate::Error> {
let time = time.unwrap_or_else(unix_time);
let payment_ids = self.payment_ids();
if payment_ids.contains(&&payment_id) {
return Err(crate::Error::DuplicatePaymentId);
}
self.amount_paid = self
.amount_paid
.checked_add(&amount)
.map_err(|_| crate::Error::AmountOverflow)?;
let payment = IncomingPayment::new(amount, payment_id, time);
self.payments.push(payment.clone());
self.changes
.get_or_insert_default()
.payments
.get_or_insert_default()
.push(payment);
Ok(())
}
#[instrument(skip(self))]
fn compute_quote_state(&self) -> MintQuoteState {
let zero_amount = Amount::new(0, self.unit.clone());
if self.amount_paid == zero_amount && self.amount_issued == zero_amount {
return MintQuoteState::Unpaid;
}
match self.amount_paid.value().cmp(&self.amount_issued.value()) {
std::cmp::Ordering::Less => {
tracing::error!("We should not have issued more then has been paid");
MintQuoteState::Issued
}
std::cmp::Ordering::Equal => MintQuoteState::Issued,
std::cmp::Ordering::Greater => MintQuoteState::Paid,
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct IncomingPayment {
pub amount: Amount<CurrencyUnit>,
pub time: u64,
pub payment_id: String,
}
impl IncomingPayment {
pub fn new(amount: Amount<CurrencyUnit>, payment_id: String, time: u64) -> Self {
Self {
payment_id,
time,
amount,
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Issuance {
pub amount: Amount<CurrencyUnit>,
pub time: u64,
}
impl Issuance {
pub fn new(amount: Amount<CurrencyUnit>, time: u64) -> Self {
Self { amount, time }
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct MeltQuote {
pub id: QuoteId,
pub unit: CurrencyUnit,
pub request: MeltPaymentRequest,
amount: Amount<CurrencyUnit>,
fee_reserve: Amount<CurrencyUnit>,
pub state: MeltQuoteState,
pub expiry: u64,
pub payment_preimage: Option<String>,
pub request_lookup_id: Option<PaymentIdentifier>,
pub options: Option<MeltOptions>,
pub created_time: u64,
pub paid_time: Option<u64>,
pub payment_method: PaymentMethod,
}
impl MeltQuote {
#[allow(clippy::too_many_arguments)]
pub fn new(
id: Option<QuoteId>,
request: MeltPaymentRequest,
unit: CurrencyUnit,
amount: Amount<CurrencyUnit>,
fee_reserve: Amount<CurrencyUnit>,
expiry: u64,
request_lookup_id: Option<PaymentIdentifier>,
options: Option<MeltOptions>,
payment_method: PaymentMethod,
) -> Self {
let id = id.unwrap_or_else(QuoteId::new_uuid);
Self {
id,
unit: unit.clone(),
request,
amount,
fee_reserve,
state: MeltQuoteState::Unpaid,
expiry,
payment_preimage: None,
request_lookup_id,
options,
created_time: unix_time(),
paid_time: None,
payment_method,
}
}
#[inline]
pub fn amount(&self) -> Amount<CurrencyUnit> {
self.amount.clone()
}
#[inline]
pub fn fee_reserve(&self) -> Amount<CurrencyUnit> {
self.fee_reserve.clone()
}
pub fn total_needed(&self) -> Result<Amount, crate::Error> {
let total = self
.amount
.checked_add(&self.fee_reserve)
.map_err(|_| crate::Error::AmountOverflow)?;
Ok(Amount::from(total.value()))
}
#[allow(clippy::too_many_arguments)]
pub fn from_db(
id: QuoteId,
unit: CurrencyUnit,
request: MeltPaymentRequest,
amount: u64,
fee_reserve: u64,
state: MeltQuoteState,
expiry: u64,
payment_preimage: Option<String>,
request_lookup_id: Option<PaymentIdentifier>,
options: Option<MeltOptions>,
created_time: u64,
paid_time: Option<u64>,
payment_method: PaymentMethod,
) -> Self {
Self {
id,
unit: unit.clone(),
request,
amount: Amount::new(amount, unit.clone()),
fee_reserve: Amount::new(fee_reserve, unit),
state,
expiry,
payment_preimage,
request_lookup_id,
options,
created_time,
paid_time,
payment_method,
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct MintKeySetInfo {
pub id: Id,
pub unit: CurrencyUnit,
pub active: bool,
pub valid_from: u64,
pub derivation_path: DerivationPath,
pub derivation_path_index: Option<u32>,
pub amounts: Vec<u64>,
#[serde(default = "default_fee")]
pub input_fee_ppk: u64,
pub final_expiry: Option<u64>,
pub issuer_version: Option<IssuerVersion>,
}
pub fn default_fee() -> u64 {
0
}
impl From<MintKeySetInfo> for KeySetInfo {
fn from(keyset_info: MintKeySetInfo) -> Self {
Self {
id: keyset_info.id,
unit: keyset_info.unit,
active: keyset_info.active,
input_fee_ppk: keyset_info.input_fee_ppk,
final_expiry: keyset_info.final_expiry,
}
}
}
impl From<MintQuote> for MintQuoteBolt11Response<QuoteId> {
fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response<QuoteId> {
MintQuoteBolt11Response {
quote: mint_quote.id.clone(),
state: mint_quote.state(),
request: mint_quote.request,
expiry: Some(mint_quote.expiry),
pubkey: mint_quote.pubkey,
amount: mint_quote.amount.map(Into::into),
unit: Some(mint_quote.unit.clone()),
}
}
}
impl From<MintQuote> for MintQuoteBolt11Response<String> {
fn from(quote: MintQuote) -> Self {
let quote: MintQuoteBolt11Response<QuoteId> = quote.into();
quote.into()
}
}
impl TryFrom<crate::mint::MintQuote> for MintQuoteBolt12Response<QuoteId> {
type Error = crate::Error;
fn try_from(mint_quote: crate::mint::MintQuote) -> Result<Self, Self::Error> {
Ok(MintQuoteBolt12Response {
quote: mint_quote.id.clone(),
request: mint_quote.request,
expiry: Some(mint_quote.expiry),
amount_paid: Amount::from(mint_quote.amount_paid.value()),
amount_issued: Amount::from(mint_quote.amount_issued.value()),
pubkey: mint_quote.pubkey.ok_or(crate::Error::PubkeyRequired)?,
amount: mint_quote.amount.map(Into::into),
unit: mint_quote.unit,
})
}
}
impl TryFrom<MintQuote> for MintQuoteBolt12Response<String> {
type Error = crate::Error;
fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
let quote: MintQuoteBolt12Response<QuoteId> = quote.try_into()?;
Ok(quote.into())
}
}
impl TryFrom<crate::mint::MintQuote> for crate::nuts::MintQuoteCustomResponse<QuoteId> {
type Error = crate::Error;
fn try_from(mint_quote: crate::mint::MintQuote) -> Result<Self, Self::Error> {
Ok(crate::nuts::MintQuoteCustomResponse {
state: mint_quote.state(),
quote: mint_quote.id.clone(),
request: mint_quote.request,
expiry: Some(mint_quote.expiry),
pubkey: mint_quote.pubkey,
amount: mint_quote.amount.map(Into::into),
unit: Some(mint_quote.unit),
extra: mint_quote.extra_json.unwrap_or_default(),
})
}
}
impl TryFrom<MintQuote> for crate::nuts::MintQuoteCustomResponse<String> {
type Error = crate::Error;
fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
let quote: crate::nuts::MintQuoteCustomResponse<QuoteId> = quote.try_into()?;
Ok(quote.into())
}
}
impl TryFrom<MintQuoteResponse<QuoteId>> for MintQuoteBolt11Response<QuoteId> {
type Error = Error;
fn try_from(response: MintQuoteResponse<QuoteId>) -> Result<Self, Self::Error> {
match response {
MintQuoteResponse::Bolt11(bolt11_response) => Ok(bolt11_response),
_ => Err(Error::InvalidPaymentMethod),
}
}
}
impl TryFrom<MintQuoteResponse<QuoteId>> for MintQuoteBolt12Response<QuoteId> {
type Error = Error;
fn try_from(response: MintQuoteResponse<QuoteId>) -> Result<Self, Self::Error> {
match response {
MintQuoteResponse::Bolt12(bolt12_response) => Ok(bolt12_response),
_ => Err(Error::InvalidPaymentMethod),
}
}
}
impl TryFrom<MintQuote> for MintQuoteResponse<QuoteId> {
type Error = Error;
fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
if quote.payment_method.is_bolt11() {
let bolt11_response: MintQuoteBolt11Response<QuoteId> = quote.into();
Ok(MintQuoteResponse::Bolt11(bolt11_response))
} else if quote.payment_method.is_bolt12() {
let bolt12_response = MintQuoteBolt12Response::try_from(quote)?;
Ok(MintQuoteResponse::Bolt12(bolt12_response))
} else {
let method = quote.payment_method.clone();
let custom_response = crate::nuts::MintQuoteCustomResponse::try_from(quote)?;
Ok(MintQuoteResponse::Custom((method, custom_response)))
}
}
}
impl From<MintQuoteResponse<QuoteId>> for MintQuoteBolt11Response<String> {
fn from(response: MintQuoteResponse<QuoteId>) -> Self {
match response {
MintQuoteResponse::Bolt11(bolt11_response) => MintQuoteBolt11Response {
quote: bolt11_response.quote.to_string(),
state: bolt11_response.state,
request: bolt11_response.request,
expiry: bolt11_response.expiry,
pubkey: bolt11_response.pubkey,
amount: bolt11_response.amount,
unit: bolt11_response.unit,
},
_ => panic!("Expected Bolt11 response"),
}
}
}
impl From<&MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
fn from(melt_quote: &MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
MeltQuoteBolt11Response {
quote: melt_quote.id.clone(),
payment_preimage: None,
change: None,
state: melt_quote.state,
expiry: melt_quote.expiry,
amount: melt_quote.amount().clone().into(),
fee_reserve: melt_quote.fee_reserve().clone().into(),
request: None,
unit: Some(melt_quote.unit.clone()),
}
}
}
impl From<MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
fn from(melt_quote: MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
MeltQuoteBolt11Response {
quote: melt_quote.id.clone(),
amount: melt_quote.amount().clone().into(),
fee_reserve: melt_quote.fee_reserve().clone().into(),
state: melt_quote.state,
expiry: melt_quote.expiry,
payment_preimage: melt_quote.payment_preimage,
change: None,
request: Some(melt_quote.request.to_string()),
unit: Some(melt_quote.unit.clone()),
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum MeltPaymentRequest {
Bolt11 {
bolt11: Bolt11Invoice,
},
Bolt12 {
#[serde(with = "offer_serde")]
offer: Box<Offer>,
},
Custom {
method: String,
request: String,
},
}
impl std::fmt::Display for MeltPaymentRequest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MeltPaymentRequest::Bolt11 { bolt11 } => write!(f, "{bolt11}"),
MeltPaymentRequest::Bolt12 { offer } => write!(f, "{offer}"),
MeltPaymentRequest::Custom { request, .. } => write!(f, "{request}"),
}
}
}
mod offer_serde {
use std::str::FromStr;
use serde::{self, Deserialize, Deserializer, Serializer};
use super::Offer;
pub fn serialize<S>(offer: &Offer, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = offer.to_string();
serializer.serialize_str(&s)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Box<Offer>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(Box::new(Offer::from_str(&s).map_err(|_| {
serde::de::Error::custom("Invalid Bolt12 Offer")
})?))
}
}