use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use super::PublicKey;
use crate::nut00::KnownMethod;
use crate::nut25::MeltQuoteBolt12Response;
use crate::nut30::{MeltQuoteOnchainResponse, MintQuoteOnchainResponse};
use crate::nuts::{
CurrencyUnit, MeltQuoteBolt11Response, MeltQuoteCustomResponse, MintQuoteBolt11Response,
MintQuoteCustomResponse, PaymentMethod, ProofState,
};
use crate::quote_id::QuoteIdError;
use crate::MintQuoteBolt12Response;
pub mod ws;
#[derive(Debug, Clone, Serialize, Eq, PartialEq, Hash, Deserialize)]
#[serde(bound = "I: DeserializeOwned + Serialize")]
pub struct Params<I> {
pub kind: Kind,
pub filters: Vec<String>,
#[serde(rename = "subId")]
pub id: I,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SupportedSettings {
pub supported: Vec<SupportedMethods>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SupportedMethods {
pub method: PaymentMethod,
pub unit: CurrencyUnit,
pub commands: Vec<WsCommand>,
}
impl SupportedMethods {
pub fn new(method: PaymentMethod, unit: CurrencyUnit, commands: Vec<WsCommand>) -> Self {
Self {
method,
unit,
commands,
}
}
pub fn default_bolt11(unit: CurrencyUnit) -> Self {
let commands = vec![
WsCommand::Bolt11MintQuote,
WsCommand::Bolt11MeltQuote,
WsCommand::ProofState,
];
Self {
method: PaymentMethod::Known(KnownMethod::Bolt11),
unit,
commands,
}
}
pub fn default_bolt12(unit: CurrencyUnit) -> Self {
let commands = vec![
WsCommand::Bolt12MintQuote,
WsCommand::Bolt12MeltQuote,
WsCommand::ProofState,
];
Self {
method: PaymentMethod::Known(KnownMethod::Bolt12),
unit,
commands,
}
}
pub fn default_custom(method: PaymentMethod, unit: CurrencyUnit) -> Self {
let method_name = method.to_string();
let commands = vec![
WsCommand::Custom(format!("{}_mint_quote", method_name)),
WsCommand::Custom(format!("{}_melt_quote", method_name)),
WsCommand::ProofState,
];
Self {
method,
unit,
commands,
}
}
}
impl WsCommand {
pub fn custom_mint_quote(method: &str) -> Self {
WsCommand::Custom(format!("{}_mint_quote", method))
}
pub fn custom_melt_quote(method: &str) -> Self {
WsCommand::Custom(format!("{}_melt_quote", method))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum WsCommand {
Bolt11MintQuote,
Bolt11MeltQuote,
Bolt12MintQuote,
Bolt12MeltQuote,
ProofState,
Custom(String),
}
impl Serialize for WsCommand {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s = match self {
WsCommand::Bolt11MintQuote => "bolt11_mint_quote",
WsCommand::Bolt11MeltQuote => "bolt11_melt_quote",
WsCommand::Bolt12MintQuote => "bolt12_mint_quote",
WsCommand::Bolt12MeltQuote => "bolt12_melt_quote",
WsCommand::ProofState => "proof_state",
WsCommand::Custom(custom) => custom.as_str(),
};
serializer.serialize_str(s)
}
}
impl<'de> Deserialize<'de> for WsCommand {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(match s.as_str() {
"bolt11_mint_quote" => WsCommand::Bolt11MintQuote,
"bolt11_melt_quote" => WsCommand::Bolt11MeltQuote,
"bolt12_mint_quote" => WsCommand::Bolt12MintQuote,
"bolt12_melt_quote" => WsCommand::Bolt12MeltQuote,
"proof_state" => WsCommand::ProofState,
custom => WsCommand::Custom(custom.to_string()),
})
}
}
impl<T> From<MintQuoteBolt12Response<T>> for NotificationPayload<T>
where
T: Clone,
{
fn from(mint_quote: MintQuoteBolt12Response<T>) -> NotificationPayload<T> {
NotificationPayload::MintQuoteBolt12Response(mint_quote)
}
}
impl<T> From<MeltQuoteBolt12Response<T>> for NotificationPayload<T>
where
T: Clone,
{
fn from(melt_quote: MeltQuoteBolt12Response<T>) -> NotificationPayload<T> {
NotificationPayload::MeltQuoteBolt12Response(melt_quote)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(bound = "T: Serialize + DeserializeOwned")]
#[serde(untagged)]
pub enum NotificationPayload<T>
where
T: Clone,
{
ProofState(ProofState),
MintQuoteOnchainResponse(MintQuoteOnchainResponse<T>),
MeltQuoteOnchainResponse(MeltQuoteOnchainResponse<T>),
MeltQuoteBolt11Response(MeltQuoteBolt11Response<T>),
MintQuoteBolt11Response(MintQuoteBolt11Response<T>),
MintQuoteBolt12Response(MintQuoteBolt12Response<T>),
MeltQuoteBolt12Response(MeltQuoteBolt12Response<T>),
CustomMintQuoteResponse(String, MintQuoteCustomResponse<T>),
CustomMeltQuoteResponse(String, MeltQuoteCustomResponse<T>),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Hash, Serialize)]
#[serde(bound = "T: Serialize + DeserializeOwned")]
pub enum NotificationId<T>
where
T: Clone,
{
ProofState(PublicKey),
MeltQuoteBolt11(T),
MintQuoteBolt11(T),
MintQuoteBolt12(T),
MeltQuoteBolt12(T),
MintQuoteOnchain(T),
MeltQuoteOnchain(T),
MintQuoteCustom(String, T),
MeltQuoteCustom(String, T),
}
#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Hash)]
pub enum Kind {
Bolt11MeltQuote,
Bolt11MintQuote,
ProofState,
Bolt12MintQuote,
Bolt12MeltQuote,
OnchainMintQuote,
OnchainMeltQuote,
Custom(String),
}
impl Serialize for Kind {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s = match self {
Kind::Bolt11MintQuote => "bolt11_mint_quote",
Kind::Bolt11MeltQuote => "bolt11_melt_quote",
Kind::Bolt12MintQuote => "bolt12_mint_quote",
Kind::Bolt12MeltQuote => "bolt12_melt_quote",
Kind::OnchainMintQuote => "onchain_mint_quote",
Kind::OnchainMeltQuote => "onchain_melt_quote",
Kind::ProofState => "proof_state",
Kind::Custom(custom) => custom.as_str(),
};
serializer.serialize_str(s)
}
}
impl<'de> Deserialize<'de> for Kind {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(match s.as_str() {
"bolt11_mint_quote" => Kind::Bolt11MintQuote,
"bolt11_melt_quote" => Kind::Bolt11MeltQuote,
"bolt12_mint_quote" => Kind::Bolt12MintQuote,
"bolt12_melt_quote" => Kind::Bolt12MeltQuote,
"onchain_mint_quote" => Kind::OnchainMintQuote,
"onchain_melt_quote" => Kind::OnchainMeltQuote,
"proof_state" => Kind::ProofState,
custom => Kind::Custom(custom.to_string()),
})
}
}
impl<I> AsRef<I> for Params<I> {
fn as_ref(&self) -> &I {
&self.id
}
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Uuid Error: {0}")]
QuoteId(#[from] QuoteIdError),
#[error("PublicKey Error: {0}")]
PublicKey(#[from] crate::nuts::nut01::Error),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nuts::nut00::CurrencyUnit;
use crate::nuts::nut01::PublicKey;
use crate::nuts::MeltQuoteState;
use crate::Amount;
#[test]
fn notification_payload_onchain_mint_roundtrip() {
let resp: MintQuoteOnchainResponse<String> = MintQuoteOnchainResponse {
quote: "abc".to_string(),
request: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh".to_string(),
unit: CurrencyUnit::Sat,
expiry: Some(1701704757),
pubkey: PublicKey::from_hex(
"03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac",
)
.unwrap(),
amount_paid: Amount::from(100_000),
amount_issued: Amount::from(0),
};
let payload: NotificationPayload<String> =
NotificationPayload::MintQuoteOnchainResponse(resp.clone());
let encoded = serde_json::to_string(&payload).unwrap();
let decoded: NotificationPayload<String> = serde_json::from_str(&encoded).unwrap();
match decoded {
NotificationPayload::MintQuoteOnchainResponse(r) => {
assert_eq!(r, resp);
}
other => panic!("expected MintQuoteOnchainResponse, got {:?}", other),
}
}
#[test]
fn notification_payload_bolt12_mint_roundtrip() {
let resp: MintQuoteBolt12Response<String> = MintQuoteBolt12Response {
quote: "abc".to_string(),
request: "lno1...".to_string(),
amount: Some(Amount::from(100_000)),
unit: CurrencyUnit::Sat,
expiry: Some(1701704757),
pubkey: PublicKey::from_hex(
"03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac",
)
.unwrap(),
amount_paid: Amount::from(0),
amount_issued: Amount::from(0),
};
let payload: NotificationPayload<String> =
NotificationPayload::MintQuoteBolt12Response(resp.clone());
let encoded = serde_json::to_string(&payload).unwrap();
let decoded: NotificationPayload<String> = serde_json::from_str(&encoded).unwrap();
match decoded {
NotificationPayload::MintQuoteBolt12Response(r) => {
assert_eq!(r, resp);
}
other => panic!("expected MintQuoteBolt12Response, got {:?}", other),
}
}
#[test]
fn notification_payload_onchain_melt_roundtrip() {
let resp: MeltQuoteOnchainResponse<String> = MeltQuoteOnchainResponse {
quote: "abc".to_string(),
amount: Amount::from(100_000),
unit: CurrencyUnit::Sat,
state: MeltQuoteState::Pending,
expiry: 1701704757,
request: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh".to_string(),
fee_options: vec![crate::nut30::MeltQuoteOnchainFeeOption {
fee_index: 0,
fee_reserve: Amount::from(5_000),
estimated_blocks: 1,
}],
selected_fee_index: Some(0),
outpoint: Some("3b7f3b85:2".to_string()),
change: None,
};
let payload: NotificationPayload<String> =
NotificationPayload::MeltQuoteOnchainResponse(resp.clone());
let encoded = serde_json::to_string(&payload).unwrap();
let decoded: NotificationPayload<String> = serde_json::from_str(&encoded).unwrap();
match decoded {
NotificationPayload::MeltQuoteOnchainResponse(r) => {
assert_eq!(r, resp);
}
other => panic!("expected MeltQuoteOnchainResponse, got {:?}", other),
}
}
}