use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use super::nut00::{BlindSignature, BlindedMessage, CurrencyUnit};
use super::nut01::PublicKey;
use super::nut05::MeltRequest;
use super::MeltQuoteState;
#[cfg(feature = "mint")]
use crate::quote_id::QuoteId;
use crate::util::serde_helpers::deserialize_empty_string_as_none;
use crate::{Amount, Proofs};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MintQuoteOnchainRequest {
pub unit: CurrencyUnit,
pub pubkey: PublicKey,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(bound = "Q: Serialize + DeserializeOwned")]
#[serde(deny_unknown_fields)]
pub struct MintQuoteOnchainResponse<Q> {
pub quote: Q,
pub request: String,
pub unit: CurrencyUnit,
pub expiry: Option<u64>,
pub pubkey: PublicKey,
#[serde(default)]
pub amount_paid: Amount,
#[serde(default)]
pub amount_issued: Amount,
}
impl<Q: ToString> MintQuoteOnchainResponse<Q> {
pub fn to_string_id(&self) -> MintQuoteOnchainResponse<String> {
MintQuoteOnchainResponse {
quote: self.quote.to_string(),
request: self.request.clone(),
unit: self.unit.clone(),
expiry: self.expiry,
pubkey: self.pubkey,
amount_paid: self.amount_paid,
amount_issued: self.amount_issued,
}
}
}
#[cfg(feature = "mint")]
impl From<MintQuoteOnchainResponse<QuoteId>> for MintQuoteOnchainResponse<String> {
fn from(value: MintQuoteOnchainResponse<QuoteId>) -> Self {
Self {
quote: value.quote.to_string(),
request: value.request,
unit: value.unit,
expiry: value.expiry,
pubkey: value.pubkey,
amount_paid: value.amount_paid,
amount_issued: value.amount_issued,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MeltQuoteOnchainRequest {
pub request: String,
pub unit: CurrencyUnit,
pub amount: Amount,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(bound = "Q: Serialize + DeserializeOwned")]
pub struct MeltOnchainRequest<Q> {
pub quote: Q,
pub fee_index: u32,
pub inputs: Proofs,
pub outputs: Option<Vec<BlindedMessage>>,
}
impl<Q> From<MeltOnchainRequest<Q>> for MeltRequest<Q>
where
Q: Serialize + DeserializeOwned,
{
fn from(request: MeltOnchainRequest<Q>) -> Self {
MeltRequest::new(request.quote, request.inputs, request.outputs)
.fee_index(request.fee_index)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MeltQuoteOnchainFeeOption {
pub fee_index: u32,
pub fee_reserve: Amount,
pub estimated_blocks: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(bound = "Q: Serialize + DeserializeOwned")]
#[serde(deny_unknown_fields)]
pub struct MeltQuoteOnchainResponse<Q> {
pub quote: Q,
pub amount: Amount,
pub unit: CurrencyUnit,
pub state: MeltQuoteState,
pub expiry: u64,
pub request: String,
pub fee_options: Vec<MeltQuoteOnchainFeeOption>,
pub selected_fee_index: Option<u32>,
#[serde(default, deserialize_with = "deserialize_empty_string_as_none")]
pub outpoint: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub change: Option<Vec<BlindSignature>>,
}
impl<Q: ToString> MeltQuoteOnchainResponse<Q> {
pub fn to_string_id(&self) -> MeltQuoteOnchainResponse<String> {
MeltQuoteOnchainResponse {
quote: self.quote.to_string(),
amount: self.amount,
unit: self.unit.clone(),
state: self.state,
expiry: self.expiry,
request: self.request.clone(),
fee_options: self.fee_options.clone(),
selected_fee_index: self.selected_fee_index,
outpoint: self.outpoint.clone(),
change: self.change.clone(),
}
}
}
#[cfg(feature = "mint")]
impl From<MeltQuoteOnchainResponse<QuoteId>> for MeltQuoteOnchainResponse<String> {
fn from(value: MeltQuoteOnchainResponse<QuoteId>) -> Self {
Self {
quote: value.quote.to_string(),
amount: value.amount,
unit: value.unit,
state: value.state,
expiry: value.expiry,
request: value.request,
fee_options: value.fee_options,
selected_fee_index: value.selected_fee_index,
outpoint: value.outpoint,
change: value.change,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mint_quote_onchain_request_serialization() {
let request = MintQuoteOnchainRequest {
unit: CurrencyUnit::Sat,
pubkey: PublicKey::from_hex(
"03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac",
)
.unwrap(),
};
let serialized = serde_json::to_string(&request).unwrap();
let deserialized: MintQuoteOnchainRequest = serde_json::from_str(&serialized).unwrap();
assert_eq!(request.unit, deserialized.unit);
assert_eq!(request.pubkey, deserialized.pubkey);
}
#[test]
fn test_melt_quote_onchain_request_serialization() {
let request = MeltQuoteOnchainRequest {
request: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh".to_string(),
unit: CurrencyUnit::Sat,
amount: Amount::from(1000),
};
let serialized = serde_json::to_string(&request).unwrap();
let deserialized: MeltQuoteOnchainRequest = serde_json::from_str(&serialized).unwrap();
assert_eq!(request.request, deserialized.request);
assert_eq!(request.unit, deserialized.unit);
assert_eq!(request.amount, deserialized.amount);
}
#[test]
fn test_melt_quote_onchain_response_serialization() {
let response: MeltQuoteOnchainResponse<String> = MeltQuoteOnchainResponse {
quote: "TRmjduhIsPxd...".to_string(),
amount: Amount::from(100000),
unit: CurrencyUnit::Sat,
state: MeltQuoteState::Pending,
expiry: 1701704757,
request: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh".to_string(),
fee_options: vec![MeltQuoteOnchainFeeOption {
fee_index: 0,
fee_reserve: Amount::from(5000),
estimated_blocks: 1,
}],
selected_fee_index: Some(0),
outpoint: Some(
"3b7f3b85c5f1a3c4d2b8e9f6a7c5d8e9f1a2b3c4d5e6f7a8b9c1d2e3f4a5b6c7:2".to_string(),
),
change: None,
};
let serialized = serde_json::to_string(&response).unwrap();
assert!(serialized.contains("\"fee_reserve\""));
assert!(serialized.contains("\"fee_index\""));
assert!(!serialized.contains("\"fee\":"));
let deserialized: MeltQuoteOnchainResponse<String> =
serde_json::from_str(&serialized).unwrap();
assert_eq!(response.quote, deserialized.quote);
assert_eq!(response.request, deserialized.request);
assert_eq!(response.amount, deserialized.amount);
assert_eq!(response.fee_options, deserialized.fee_options);
assert_eq!(response.selected_fee_index, deserialized.selected_fee_index);
assert_eq!(response.state, deserialized.state);
assert_eq!(response.outpoint, deserialized.outpoint);
assert_eq!(response.change, deserialized.change);
}
#[test]
fn test_melt_quote_onchain_response_serializes_null_outpoint() {
let response: MeltQuoteOnchainResponse<String> = MeltQuoteOnchainResponse {
quote: "TRmjduhIsPxd...".to_string(),
amount: Amount::from(100000),
unit: CurrencyUnit::Sat,
state: MeltQuoteState::Pending,
expiry: 1701704757,
request: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh".to_string(),
fee_options: vec![MeltQuoteOnchainFeeOption {
fee_index: 0,
fee_reserve: Amount::from(5000),
estimated_blocks: 1,
}],
selected_fee_index: None,
outpoint: None,
change: None,
};
let serialized = serde_json::to_string(&response).unwrap();
assert!(serialized.contains("\"outpoint\":null"));
let deserialized: MeltQuoteOnchainResponse<String> =
serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.outpoint, None);
}
#[test]
fn test_mint_quote_onchain_response_to_string_id() {
use crate::nuts::nut00::CurrencyUnit;
use crate::nuts::nut01::PublicKey;
use crate::Amount;
let response: MintQuoteOnchainResponse<String> = MintQuoteOnchainResponse {
quote: "DSGLX9kevM...".to_string(),
request: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh".to_string(),
unit: CurrencyUnit::Sat,
expiry: Some(1701704757),
pubkey: PublicKey::from_hex(
"03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac",
)
.unwrap(),
amount_paid: Amount::from(100000),
amount_issued: Amount::from(0),
};
let string_id_response = response.to_string_id();
assert_eq!(string_id_response.quote, "DSGLX9kevM...");
}
#[test]
fn test_melt_quote_onchain_response_to_string_id() {
use crate::nuts::nut00::CurrencyUnit;
use crate::Amount;
let response: MeltQuoteOnchainResponse<String> = MeltQuoteOnchainResponse {
quote: "TRmjduhIsPxd...".to_string(),
amount: Amount::from(100000),
unit: CurrencyUnit::Sat,
state: MeltQuoteState::Pending,
expiry: 1701704757,
request: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh".to_string(),
fee_options: vec![MeltQuoteOnchainFeeOption {
fee_index: 0,
fee_reserve: Amount::from(5000),
estimated_blocks: 1,
}],
selected_fee_index: Some(0),
outpoint: Some(
"3b7f3b85c5f1a3c4d2b8e9f6a7c5d8e9f1a2b3c4d5e6f7a8b9c1d2e3f4a5b6c7:2".to_string(),
),
change: None,
};
let string_id_response = response.to_string_id();
assert_eq!(string_id_response.quote, "TRmjduhIsPxd...");
}
}