compatibility for migrating Nutshell Mints quote ids (#984)

This commit is contained in:
lollerfirst
2025-08-27 18:12:35 +02:00
committed by GitHub
parent 971957b839
commit f1118b1c7b
33 changed files with 446 additions and 187 deletions

View File

@@ -44,4 +44,3 @@ uuid = { workspace = true, features = ["js"], optional = true }
[dev-dependencies]
bip39.workspace = true
uuid.workspace = true

View File

@@ -25,3 +25,107 @@ macro_rules! ensure_cdk {
}
};
}
#[cfg(feature = "mint")]
/// Quote ID. The specifications only define a string but CDK uses Uuid, so we use an enum to port compatibility.
pub mod quote_id {
use std::fmt;
use std::str::FromStr;
use bitcoin::base64::engine::general_purpose;
use bitcoin::base64::Engine as _;
use serde::{de, Deserialize, Deserializer, Serialize};
use thiserror::Error;
use uuid::Uuid;
/// Invalid UUID
#[derive(Debug, Error)]
pub enum QuoteIdError {
/// UUID Error
#[error("invalid UUID: {0}")]
Uuid(#[from] uuid::Error),
/// Invalid base64
#[error("invalid base64")]
Base64,
/// Invalid quote ID
#[error("neither a valid UUID nor a valid base64 string")]
InvalidQuoteId,
}
/// Mint Quote ID
#[derive(Serialize, Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
#[serde(untagged)]
pub enum QuoteId {
/// (Nutshell) base64 quote ID
BASE64(String),
/// UUID quote ID
UUID(Uuid),
}
impl QuoteId {
/// Create a new UUID-based MintQuoteId
pub fn new_uuid() -> Self {
Self::UUID(Uuid::new_v4())
}
}
impl From<Uuid> for QuoteId {
fn from(uuid: Uuid) -> Self {
Self::UUID(uuid)
}
}
impl fmt::Display for QuoteId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
QuoteId::BASE64(s) => write!(f, "{}", s),
QuoteId::UUID(u) => write!(f, "{}", u),
}
}
}
impl FromStr for QuoteId {
type Err = QuoteIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// Try UUID first
if let Ok(u) = Uuid::parse_str(s) {
return Ok(QuoteId::UUID(u));
}
// Try base64: decode, then re-encode and compare to ensure canonical form
// Use the standard (URL/filename safe or standard) depending on your needed alphabet.
// Here we use standard base64.
match general_purpose::URL_SAFE.decode(s) {
Ok(_bytes) => Ok(QuoteId::BASE64(s.to_string())),
Err(_) => Err(QuoteIdError::InvalidQuoteId),
}
}
}
impl<'de> Deserialize<'de> for QuoteId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// Deserialize as plain string first
let s = String::deserialize(deserializer)?;
// Try UUID first
if let Ok(u) = Uuid::parse_str(&s) {
return Ok(QuoteId::UUID(u));
}
if general_purpose::URL_SAFE.decode(&s).is_ok() {
return Ok(QuoteId::BASE64(s));
}
// Neither matched — return a helpful error
Err(de::Error::custom(format!(
"QuoteId must be either a UUID (e.g. {}) or a valid base64 string; got: {}",
Uuid::nil(),
s
)))
}
}
}

View File

@@ -10,10 +10,12 @@ use serde::de::{self, DeserializeOwned, Deserializer, MapAccess, Visitor};
use serde::ser::{SerializeStruct, Serializer};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[cfg(feature = "mint")]
use uuid::Uuid;
use super::nut00::{BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod};
#[cfg(feature = "mint")]
use crate::quote_id::QuoteId;
#[cfg(feature = "mint")]
use crate::quote_id::QuoteIdError;
use crate::Amount;
/// NUT04 Error
@@ -44,12 +46,12 @@ pub struct MintRequest<Q> {
}
#[cfg(feature = "mint")]
impl TryFrom<MintRequest<String>> for MintRequest<Uuid> {
type Error = uuid::Error;
impl TryFrom<MintRequest<String>> for MintRequest<QuoteId> {
type Error = QuoteIdError;
fn try_from(value: MintRequest<String>) -> Result<Self, Self::Error> {
Ok(Self {
quote: Uuid::from_str(&value.quote)?,
quote: QuoteId::from_str(&value.quote)?,
outputs: value.outputs,
signature: value.signature,
})

View File

@@ -9,11 +9,11 @@ use serde::de::{self, DeserializeOwned, Deserializer, MapAccess, Visitor};
use serde::ser::{SerializeStruct, Serializer};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[cfg(feature = "mint")]
use uuid::Uuid;
use super::nut00::{BlindedMessage, CurrencyUnit, PaymentMethod, Proofs};
use super::ProofsMethods;
#[cfg(feature = "mint")]
use crate::quote_id::QuoteId;
use crate::Amount;
/// NUT05 Error
@@ -28,6 +28,9 @@ pub enum Error {
/// Unsupported unit
#[error("Unsupported unit")]
UnsupportedUnit,
/// Invalid quote id
#[error("Invalid quote id")]
InvalidQuote,
}
/// Possible states of a quote
@@ -91,12 +94,12 @@ pub struct MeltRequest<Q> {
}
#[cfg(feature = "mint")]
impl TryFrom<MeltRequest<String>> for MeltRequest<Uuid> {
type Error = uuid::Error;
impl TryFrom<MeltRequest<String>> for MeltRequest<QuoteId> {
type Error = Error;
fn try_from(value: MeltRequest<String>) -> Result<Self, Self::Error> {
Ok(Self {
quote: Uuid::from_str(&value.quote)?,
quote: QuoteId::from_str(&value.quote).map_err(|_e| Error::InvalidQuote)?,
inputs: value.inputs,
outputs: value.outputs,
})

View File

@@ -1262,6 +1262,7 @@ impl<'de> Deserialize<'de> for Tag {
}
}
#[cfg(feature = "mint")]
#[cfg(test)]
mod tests {
use std::str::FromStr;
@@ -1270,6 +1271,7 @@ mod tests {
use super::*;
use crate::nuts::Id;
use crate::quote_id::QuoteId;
use crate::secret::Secret;
use crate::{Amount, BlindedMessage};
@@ -1514,7 +1516,11 @@ mod tests {
let blinded_msg = create_test_blinded_msg(pubkey);
// Create melt request
let mut melt = MeltRequest::new(Uuid::new_v4(), vec![proof], Some(vec![blinded_msg]));
let mut melt = MeltRequest::new(
QuoteId::UUID(Uuid::new_v4()),
vec![proof],
Some(vec![blinded_msg]),
);
// Before signing, should fail verification
assert!(

View File

@@ -1,14 +1,14 @@
//! Specific Subscription for the cdk crate
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
#[cfg(feature = "mint")]
use uuid::Uuid;
#[cfg(feature = "mint")]
use super::PublicKey;
use crate::nuts::{
CurrencyUnit, MeltQuoteBolt11Response, MintQuoteBolt11Response, PaymentMethod, ProofState,
};
#[cfg(feature = "mint")]
use crate::quote_id::{QuoteId, QuoteIdError};
use crate::MintQuoteBolt12Response;
pub mod ws;
@@ -154,14 +154,14 @@ impl<T> From<MintQuoteBolt11Response<T>> for NotificationPayload<T> {
pub enum Notification {
/// ProofState id is a Pubkey
ProofState(PublicKey),
/// MeltQuote id is an Uuid
MeltQuoteBolt11(Uuid),
/// MintQuote id is an Uuid
MintQuoteBolt11(Uuid),
/// MintQuote id is an Uuid
MintQuoteBolt12(Uuid),
/// MintQuote id is an Uuid
MeltQuoteBolt12(Uuid),
/// MeltQuote id is an QuoteId
MeltQuoteBolt11(QuoteId),
/// MintQuote id is an QuoteId
MintQuoteBolt11(QuoteId),
/// MintQuote id is an QuoteId
MintQuoteBolt12(QuoteId),
/// MintQuote id is an QuoteId
MeltQuoteBolt12(QuoteId),
}
/// Kind
@@ -190,7 +190,7 @@ pub enum Error {
#[cfg(feature = "mint")]
#[error("Uuid Error: {0}")]
/// Uuid Error
Uuid(#[from] uuid::Error),
QuoteId(#[from] QuoteIdError),
#[error("PublicKey Error: {0}")]
/// PublicKey Error

View File

@@ -74,8 +74,6 @@ where
#[cfg(test)]
mod tests {
use uuid::Uuid;
use super::*;
#[test]
@@ -111,8 +109,11 @@ mod tests {
assert_eq!(expected_msg_to_sign, request_msg_to_sign);
}
#[cfg(feature = "mint")]
#[test]
fn test_valid_signature() {
use uuid::Uuid;
let pubkey = PublicKey::from_hex(
"03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac",
)

View File

@@ -8,10 +8,10 @@ use serde::de::DeserializeOwned;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use thiserror::Error;
#[cfg(feature = "mint")]
use uuid::Uuid;
use super::{BlindSignature, CurrencyUnit, MeltQuoteState, Mpp, PublicKey};
#[cfg(feature = "mint")]
use crate::quote_id::QuoteId;
use crate::Amount;
/// NUT023 Error
@@ -120,8 +120,8 @@ impl<Q: ToString> MintQuoteBolt11Response<Q> {
}
#[cfg(feature = "mint")]
impl From<MintQuoteBolt11Response<Uuid>> for MintQuoteBolt11Response<String> {
fn from(value: MintQuoteBolt11Response<Uuid>) -> Self {
impl From<MintQuoteBolt11Response<QuoteId>> for MintQuoteBolt11Response<String> {
fn from(value: MintQuoteBolt11Response<QuoteId>) -> Self {
Self {
quote: value.quote.to_string(),
request: value.request,
@@ -293,8 +293,8 @@ impl<Q: ToString> MeltQuoteBolt11Response<Q> {
}
#[cfg(feature = "mint")]
impl From<MeltQuoteBolt11Response<Uuid>> for MeltQuoteBolt11Response<String> {
fn from(value: MeltQuoteBolt11Response<Uuid>) -> Self {
impl From<MeltQuoteBolt11Response<QuoteId>> for MeltQuoteBolt11Response<String> {
fn from(value: MeltQuoteBolt11Response<QuoteId>) -> Self {
Self {
quote: value.quote.to_string(),
amount: value.amount,

View File

@@ -1,10 +1,10 @@
//! Bolt12
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[cfg(feature = "mint")]
use uuid::Uuid;
use super::{CurrencyUnit, MeltOptions, PublicKey};
#[cfg(feature = "mint")]
use crate::quote_id::QuoteId;
use crate::Amount;
/// NUT18 Error
@@ -76,8 +76,8 @@ impl<Q: ToString> MintQuoteBolt12Response<Q> {
}
#[cfg(feature = "mint")]
impl From<MintQuoteBolt12Response<Uuid>> for MintQuoteBolt12Response<String> {
fn from(value: MintQuoteBolt12Response<Uuid>) -> Self {
impl From<MintQuoteBolt12Response<QuoteId>> for MintQuoteBolt12Response<String> {
fn from(value: MintQuoteBolt12Response<QuoteId>) -> Self {
Self {
quote: value.quote.to_string(),
request: value.request,

View File

@@ -3,6 +3,7 @@ use axum::extract::{Json, Path, State};
use axum::response::Response;
#[cfg(feature = "swagger")]
use cdk::error::ErrorResponse;
use cdk::mint::QuoteId;
#[cfg(feature = "auth")]
use cdk::nuts::nut21::{Method, ProtectedEndpoint, RoutePath};
use cdk::nuts::{
@@ -11,17 +12,16 @@ use cdk::nuts::{
};
use paste::paste;
use tracing::instrument;
use uuid::Uuid;
#[cfg(feature = "auth")]
use crate::auth::AuthHeader;
use crate::{into_response, post_cache_wrapper, MintState};
post_cache_wrapper!(post_mint_bolt12, MintRequest<Uuid>, MintResponse);
post_cache_wrapper!(post_mint_bolt12, MintRequest<QuoteId>, MintResponse);
post_cache_wrapper!(
post_melt_bolt12,
MeltRequest<Uuid>,
MeltQuoteBolt11Response<Uuid>
MeltRequest<QuoteId>,
MeltQuoteBolt11Response<QuoteId>
);
#[cfg_attr(feature = "swagger", utoipa::path(
@@ -38,7 +38,7 @@ pub async fn post_mint_bolt12_quote(
#[cfg(feature = "auth")] auth: AuthHeader,
State(state): State<MintState>,
Json(payload): Json<MintQuoteBolt12Request>,
) -> Result<Json<MintQuoteBolt12Response<Uuid>>, Response> {
) -> Result<Json<MintQuoteBolt12Response<QuoteId>>, Response> {
#[cfg(feature = "auth")]
{
state
@@ -77,8 +77,8 @@ pub async fn post_mint_bolt12_quote(
pub async fn get_check_mint_bolt12_quote(
#[cfg(feature = "auth")] auth: AuthHeader,
State(state): State<MintState>,
Path(quote_id): Path<Uuid>,
) -> Result<Json<MintQuoteBolt12Response<Uuid>>, Response> {
Path(quote_id): Path<QuoteId>,
) -> Result<Json<MintQuoteBolt12Response<QuoteId>>, Response> {
#[cfg(feature = "auth")]
{
state
@@ -115,7 +115,7 @@ pub async fn get_check_mint_bolt12_quote(
pub async fn post_mint_bolt12(
#[cfg(feature = "auth")] auth: AuthHeader,
State(state): State<MintState>,
Json(payload): Json<MintRequest<Uuid>>,
Json(payload): Json<MintRequest<QuoteId>>,
) -> Result<Json<MintResponse>, Response> {
#[cfg(feature = "auth")]
{
@@ -155,7 +155,7 @@ pub async fn post_melt_bolt12_quote(
#[cfg(feature = "auth")] auth: AuthHeader,
State(state): State<MintState>,
Json(payload): Json<MeltQuoteBolt12Request>,
) -> Result<Json<MeltQuoteBolt11Response<Uuid>>, Response> {
) -> Result<Json<MeltQuoteBolt11Response<QuoteId>>, Response> {
#[cfg(feature = "auth")]
{
state
@@ -193,8 +193,8 @@ pub async fn post_melt_bolt12_quote(
pub async fn post_melt_bolt12(
#[cfg(feature = "auth")] auth: AuthHeader,
State(state): State<MintState>,
Json(payload): Json<MeltRequest<Uuid>>,
) -> Result<Json<MeltQuoteBolt11Response<Uuid>>, Response> {
Json(payload): Json<MeltRequest<QuoteId>>,
) -> Result<Json<MeltQuoteBolt11Response<QuoteId>>, Response> {
#[cfg(feature = "auth")]
{
state

View File

@@ -4,6 +4,7 @@ use axum::extract::{Json, Path, State};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use cdk::error::{ErrorCode, ErrorResponse};
use cdk::mint::QuoteId;
#[cfg(feature = "auth")]
use cdk::nuts::nut21::{Method, ProtectedEndpoint, RoutePath};
use cdk::nuts::{
@@ -15,7 +16,6 @@ use cdk::nuts::{
use cdk::util::unix_time;
use paste::paste;
use tracing::instrument;
use uuid::Uuid;
#[cfg(feature = "auth")]
use crate::auth::AuthHeader;
@@ -62,11 +62,11 @@ macro_rules! post_cache_wrapper {
}
post_cache_wrapper!(post_swap, SwapRequest, SwapResponse);
post_cache_wrapper!(post_mint_bolt11, MintRequest<Uuid>, MintResponse);
post_cache_wrapper!(post_mint_bolt11, MintRequest<QuoteId>, MintResponse);
post_cache_wrapper!(
post_melt_bolt11,
MeltRequest<Uuid>,
MeltQuoteBolt11Response<Uuid>
MeltRequest<QuoteId>,
MeltQuoteBolt11Response<QuoteId>
);
#[cfg_attr(feature = "swagger", utoipa::path(
@@ -152,7 +152,7 @@ pub(crate) async fn post_mint_bolt11_quote(
#[cfg(feature = "auth")] auth: AuthHeader,
State(state): State<MintState>,
Json(payload): Json<MintQuoteBolt11Request>,
) -> Result<Json<MintQuoteBolt11Response<Uuid>>, Response> {
) -> Result<Json<MintQuoteBolt11Response<QuoteId>>, Response> {
#[cfg(feature = "auth")]
state
.mint
@@ -191,8 +191,8 @@ pub(crate) async fn post_mint_bolt11_quote(
pub(crate) async fn get_check_mint_bolt11_quote(
#[cfg(feature = "auth")] auth: AuthHeader,
State(state): State<MintState>,
Path(quote_id): Path<Uuid>,
) -> Result<Json<MintQuoteBolt11Response<Uuid>>, Response> {
Path(quote_id): Path<QuoteId>,
) -> Result<Json<MintQuoteBolt11Response<QuoteId>>, Response> {
#[cfg(feature = "auth")]
{
state
@@ -244,7 +244,7 @@ pub(crate) async fn ws_handler(
pub(crate) async fn post_mint_bolt11(
#[cfg(feature = "auth")] auth: AuthHeader,
State(state): State<MintState>,
Json(payload): Json<MintRequest<Uuid>>,
Json(payload): Json<MintRequest<QuoteId>>,
) -> Result<Json<MintResponse>, Response> {
#[cfg(feature = "auth")]
{
@@ -286,7 +286,7 @@ pub(crate) async fn post_melt_bolt11_quote(
#[cfg(feature = "auth")] auth: AuthHeader,
State(state): State<MintState>,
Json(payload): Json<MeltQuoteBolt11Request>,
) -> Result<Json<MeltQuoteBolt11Response<Uuid>>, Response> {
) -> Result<Json<MeltQuoteBolt11Response<QuoteId>>, Response> {
#[cfg(feature = "auth")]
{
state
@@ -327,8 +327,8 @@ pub(crate) async fn post_melt_bolt11_quote(
pub(crate) async fn get_check_melt_bolt11_quote(
#[cfg(feature = "auth")] auth: AuthHeader,
State(state): State<MintState>,
Path(quote_id): Path<Uuid>,
) -> Result<Json<MeltQuoteBolt11Response<Uuid>>, Response> {
Path(quote_id): Path<QuoteId>,
) -> Result<Json<MeltQuoteBolt11Response<QuoteId>>, Response> {
#[cfg(feature = "auth")]
{
state
@@ -370,8 +370,8 @@ pub(crate) async fn get_check_melt_bolt11_quote(
pub(crate) async fn post_melt_bolt11(
#[cfg(feature = "auth")] auth: AuthHeader,
State(state): State<MintState>,
Json(payload): Json<MeltRequest<Uuid>>,
) -> Result<Json<MeltQuoteBolt11Response<Uuid>>, Response> {
Json(payload): Json<MeltRequest<QuoteId>>,
) -> Result<Json<MeltQuoteBolt11Response<QuoteId>>, Response> {
#[cfg(feature = "auth")]
{
state

View File

@@ -1,6 +1,7 @@
use std::collections::HashMap;
use axum::extract::ws::{Message, WebSocket};
use cdk::mint::QuoteId;
use cdk::nuts::nut17::NotificationPayload;
use cdk::pub_sub::SubId;
use cdk::ws::{
@@ -9,7 +10,6 @@ use cdk::ws::{
};
use futures::StreamExt;
use tokio::sync::mpsc;
use uuid::Uuid;
use crate::MintState;
@@ -37,7 +37,7 @@ pub use error::WsError;
pub struct WsContext {
state: MintState,
subscriptions: HashMap<SubId, tokio::task::JoinHandle<()>>,
publisher: mpsc::Sender<(SubId, NotificationPayload<Uuid>)>,
publisher: mpsc::Sender<(SubId, NotificationPayload<QuoteId>)>,
}
/// Main function for websocket connections

View File

@@ -3,6 +3,7 @@
use std::collections::HashMap;
use async_trait::async_trait;
use cashu::quote_id::QuoteId;
use cashu::{Amount, MintInfo};
use uuid::Uuid;
@@ -64,25 +65,27 @@ pub trait QuotesTransaction<'a> {
type Err: Into<Error> + From<Error>;
/// Get [`MintMintQuote`] and lock it for update in this transaction
async fn get_mint_quote(&mut self, quote_id: &Uuid)
-> Result<Option<MintMintQuote>, Self::Err>;
async fn get_mint_quote(
&mut self,
quote_id: &QuoteId,
) -> Result<Option<MintMintQuote>, Self::Err>;
/// Add [`MintMintQuote`]
async fn add_mint_quote(&mut self, quote: MintMintQuote) -> Result<(), Self::Err>;
/// Increment amount paid [`MintMintQuote`]
async fn increment_mint_quote_amount_paid(
&mut self,
quote_id: &Uuid,
quote_id: &QuoteId,
amount_paid: Amount,
payment_id: String,
) -> Result<Amount, Self::Err>;
/// Increment amount paid [`MintMintQuote`]
async fn increment_mint_quote_amount_issued(
&mut self,
quote_id: &Uuid,
quote_id: &QuoteId,
amount_issued: Amount,
) -> Result<Amount, Self::Err>;
/// Remove [`MintMintQuote`]
async fn remove_mint_quote(&mut self, quote_id: &Uuid) -> Result<(), Self::Err>;
async fn remove_mint_quote(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err>;
/// Get [`mint::MeltQuote`] and lock it for update in this transaction
async fn get_melt_quote(
&mut self,
@@ -94,7 +97,7 @@ pub trait QuotesTransaction<'a> {
/// Updates the request lookup id for a melt quote
async fn update_melt_quote_request_lookup_id(
&mut self,
quote_id: &Uuid,
quote_id: &QuoteId,
new_request_lookup_id: &PaymentIdentifier,
) -> Result<(), Self::Err>;
@@ -103,7 +106,7 @@ pub trait QuotesTransaction<'a> {
/// It is expected for this function to fail if the state is already set to the new state
async fn update_melt_quote_state(
&mut self,
quote_id: &Uuid,
quote_id: &QuoteId,
new_state: MeltQuoteState,
payment_proof: Option<String>,
) -> Result<(MeltQuoteState, mint::MeltQuote), Self::Err>;
@@ -129,7 +132,7 @@ pub trait QuotesDatabase {
type Err: Into<Error> + From<Error>;
/// Get [`MintMintQuote`]
async fn get_mint_quote(&self, quote_id: &Uuid) -> Result<Option<MintMintQuote>, Self::Err>;
async fn get_mint_quote(&self, quote_id: &QuoteId) -> Result<Option<MintMintQuote>, Self::Err>;
/// Get all [`MintMintQuote`]s
async fn get_mint_quote_by_request(
@@ -144,7 +147,10 @@ pub trait QuotesDatabase {
/// Get Mint Quotes
async fn get_mint_quotes(&self) -> Result<Vec<MintMintQuote>, Self::Err>;
/// Get [`mint::MeltQuote`]
async fn get_melt_quote(&self, quote_id: &Uuid) -> Result<Option<mint::MeltQuote>, Self::Err>;
async fn get_melt_quote(
&self,
quote_id: &QuoteId,
) -> Result<Option<mint::MeltQuote>, Self::Err>;
/// Get all [`mint::MeltQuote`]s
async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err>;
}
@@ -205,7 +211,7 @@ pub trait SignaturesTransaction<'a> {
&mut self,
blinded_messages: &[PublicKey],
blind_signatures: &[BlindSignature],
quote_id: Option<Uuid>,
quote_id: Option<QuoteId>,
) -> Result<(), Self::Err>;
/// Get [`BlindSignature`]s
@@ -234,7 +240,7 @@ pub trait SignaturesDatabase {
/// Get [`BlindSignature`]s for quote
async fn get_blind_signatures_for_quote(
&self,
quote_id: &Uuid,
quote_id: &QuoteId,
) -> Result<Vec<BlindSignature>, Self::Err>;
}

View File

@@ -126,6 +126,13 @@ pub enum Error {
#[error(transparent)]
#[cfg(feature = "auth")]
NUT22(#[from] crate::nuts::nut22::Error),
/// NUT04 Error
#[error(transparent)]
NUT04(#[from] crate::nuts::nut04::Error),
/// Quote ID Error
#[error(transparent)]
#[cfg(feature = "mint")]
QuoteId(#[from] crate::quote_id::QuoteIdError),
/// Serde Error
#[error(transparent)]
Serde(#[from] serde_json::Error),

View File

@@ -363,6 +363,10 @@ pub enum Error {
/// NUT23 Error
#[error(transparent)]
NUT23(#[from] crate::nuts::nut23::Error),
/// Quote ID Error
#[error(transparent)]
#[cfg(feature = "mint")]
QuoteId(#[from] crate::quote_id::QuoteIdError),
/// From slice error
#[error(transparent)]
TryFromSliceError(#[from] TryFromSliceError),

View File

@@ -29,5 +29,7 @@ pub use bitcoin;
pub use cashu::amount::{self, Amount};
pub use cashu::lightning_invoice::{self, Bolt11Invoice};
pub use cashu::nuts::{self, *};
#[cfg(feature = "mint")]
pub use cashu::quote_id::{self, *};
pub use cashu::{dhke, ensure_cdk, mint_url, secret, util, SECP256K1};
pub use error::Error;

View File

@@ -1,6 +1,7 @@
//! Mint types
use bitcoin::bip32::DerivationPath;
use cashu::quote_id::QuoteId;
use cashu::util::unix_time;
use cashu::{
Bolt11Invoice, MeltOptions, MeltQuoteBolt11Response, MintQuoteBolt11Response,
@@ -19,7 +20,7 @@ use crate::{Amount, CurrencyUnit, Id, KeySetInfo, PublicKey};
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct MintQuote {
/// Quote id
pub id: Uuid,
pub id: QuoteId,
/// Amount of quote
pub amount: Option<Amount>,
/// Unit of quote
@@ -56,7 +57,7 @@ impl MintQuote {
/// Create new [`MintQuote`]
#[allow(clippy::too_many_arguments)]
pub fn new(
id: Option<Uuid>,
id: Option<QuoteId>,
request: String,
unit: CurrencyUnit,
amount: Option<Amount>,
@@ -70,7 +71,7 @@ impl MintQuote {
payments: Vec<IncomingPayment>,
issuance: Vec<Issuance>,
) -> Self {
let id = id.unwrap_or(Uuid::new_v4());
let id = id.unwrap_or_else(QuoteId::new_uuid);
Self {
id,
@@ -230,7 +231,7 @@ impl Issuance {
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct MeltQuote {
/// Quote id
pub id: Uuid,
pub id: QuoteId,
/// Quote unit
pub unit: CurrencyUnit,
/// Quote amount
@@ -277,7 +278,7 @@ impl MeltQuote {
let id = Uuid::new_v4();
Self {
id,
id: QuoteId::UUID(id),
amount,
unit,
request,
@@ -336,10 +337,10 @@ impl From<MintKeySetInfo> for KeySetInfo {
}
}
impl From<MintQuote> for MintQuoteBolt11Response<Uuid> {
fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response<Uuid> {
impl From<MintQuote> for MintQuoteBolt11Response<QuoteId> {
fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response<QuoteId> {
MintQuoteBolt11Response {
quote: mint_quote.id,
quote: mint_quote.id.clone(),
state: mint_quote.state(),
request: mint_quote.request,
expiry: Some(mint_quote.expiry),
@@ -352,18 +353,18 @@ impl From<MintQuote> for MintQuoteBolt11Response<Uuid> {
impl From<MintQuote> for MintQuoteBolt11Response<String> {
fn from(quote: MintQuote) -> Self {
let quote: MintQuoteBolt11Response<Uuid> = quote.into();
let quote: MintQuoteBolt11Response<QuoteId> = quote.into();
quote.into()
}
}
impl TryFrom<crate::mint::MintQuote> for MintQuoteBolt12Response<Uuid> {
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,
quote: mint_quote.id.clone(),
request: mint_quote.request,
expiry: Some(mint_quote.expiry),
amount_paid: mint_quote.amount_paid,
@@ -379,16 +380,16 @@ impl TryFrom<MintQuote> for MintQuoteBolt12Response<String> {
type Error = crate::Error;
fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
let quote: MintQuoteBolt12Response<Uuid> = quote.try_into()?;
let quote: MintQuoteBolt12Response<QuoteId> = quote.try_into()?;
Ok(quote.into())
}
}
impl From<&MeltQuote> for MeltQuoteBolt11Response<Uuid> {
fn from(melt_quote: &MeltQuote) -> MeltQuoteBolt11Response<Uuid> {
impl From<&MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
fn from(melt_quote: &MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
MeltQuoteBolt11Response {
quote: melt_quote.id,
quote: melt_quote.id.clone(),
payment_preimage: None,
change: None,
state: melt_quote.state,
@@ -402,11 +403,11 @@ impl From<&MeltQuote> for MeltQuoteBolt11Response<Uuid> {
}
}
impl From<MeltQuote> for MeltQuoteBolt11Response<Uuid> {
fn from(melt_quote: MeltQuote) -> MeltQuoteBolt11Response<Uuid> {
impl From<MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
fn from(melt_quote: MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
let paid = melt_quote.state == MeltQuoteState::Paid;
MeltQuoteBolt11Response {
quote: melt_quote.id,
quote: melt_quote.id.clone(),
amount: melt_quote.amount,
fee_reserve: melt_quote.fee_reserve,
paid: Some(paid),

View File

@@ -6,11 +6,11 @@ use cashu::nut17::{self};
#[cfg(feature = "mint")]
use cashu::nut17::{Error, Kind, Notification};
#[cfg(feature = "mint")]
use cashu::quote_id::QuoteId;
#[cfg(feature = "mint")]
use cashu::{NotificationPayload, PublicKey};
#[cfg(feature = "mint")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "mint")]
use uuid::Uuid;
#[cfg(feature = "mint")]
use crate::pub_sub::index::{Index, Indexable, SubscriptionGlobalId};
@@ -45,14 +45,14 @@ impl TryFrom<IndexableParams> for Vec<Index<Notification>> {
.map(|filter| {
let idx = match params.kind {
Kind::Bolt11MeltQuote => {
Notification::MeltQuoteBolt11(Uuid::from_str(&filter)?)
Notification::MeltQuoteBolt11(QuoteId::from_str(&filter)?)
}
Kind::Bolt11MintQuote => {
Notification::MintQuoteBolt11(Uuid::from_str(&filter)?)
Notification::MintQuoteBolt11(QuoteId::from_str(&filter)?)
}
Kind::ProofState => Notification::ProofState(PublicKey::from_str(&filter)?),
Kind::Bolt12MintQuote => {
Notification::MintQuoteBolt12(Uuid::from_str(&filter)?)
Notification::MintQuoteBolt12(QuoteId::from_str(&filter)?)
}
};
@@ -70,7 +70,7 @@ impl AsRef<SubId> for IndexableParams {
}
#[cfg(feature = "mint")]
impl Indexable for NotificationPayload<Uuid> {
impl Indexable for NotificationPayload<QuoteId> {
type Type = Notification;
fn to_indexes(&self) -> Vec<Index<Self::Type>> {
@@ -79,13 +79,19 @@ impl Indexable for NotificationPayload<Uuid> {
vec![Index::from(Notification::ProofState(proof_state.y))]
}
NotificationPayload::MeltQuoteBolt11Response(melt_quote) => {
vec![Index::from(Notification::MeltQuoteBolt11(melt_quote.quote))]
vec![Index::from(Notification::MeltQuoteBolt11(
melt_quote.quote.clone(),
))]
}
NotificationPayload::MintQuoteBolt11Response(mint_quote) => {
vec![Index::from(Notification::MintQuoteBolt11(mint_quote.quote))]
vec![Index::from(Notification::MintQuoteBolt11(
mint_quote.quote.clone(),
))]
}
NotificationPayload::MintQuoteBolt12Response(mint_quote) => {
vec![Index::from(Notification::MintQuoteBolt12(mint_quote.quote))]
vec![Index::from(Notification::MintQuoteBolt12(
mint_quote.quote.clone(),
))]
}
}
}

View File

@@ -6,9 +6,9 @@
use cashu::nut17::ws::JSON_RPC_VERSION;
use cashu::nut17::{self};
#[cfg(feature = "mint")]
use cashu::NotificationPayload;
use cashu::quote_id::QuoteId;
#[cfg(feature = "mint")]
use uuid::Uuid;
use cashu::NotificationPayload;
use crate::pub_sub::SubId;
@@ -48,7 +48,7 @@ pub type NotificationInner<T> = nut17::ws::NotificationInner<T, SubId>;
#[cfg(feature = "mint")]
/// Converts a notification with UUID identifiers to a notification with string identifiers
pub fn notification_uuid_to_notification_string(
notification: NotificationInner<Uuid>,
notification: NotificationInner<QuoteId>,
) -> NotificationInner<String> {
nut17::ws::NotificationInner {
sub_id: notification.sub_id,
@@ -69,7 +69,7 @@ pub fn notification_uuid_to_notification_string(
#[cfg(feature = "mint")]
/// Converts a notification to a websocket message that can be sent to clients
pub fn notification_to_ws_message(notification: NotificationInner<Uuid>) -> WsMessageOrResponse {
pub fn notification_to_ws_message(notification: NotificationInner<QuoteId>) -> WsMessageOrResponse {
nut17::ws::WsMessageOrResponse::Notification(nut17::ws::WsNotification {
jsonrpc: JSON_RPC_VERSION.to_owned(),
method: "subscribe".to_string(),

View File

@@ -260,6 +260,7 @@ fn create_ldk_settings(
url: format!("http://127.0.0.1:{port}"),
listen_host: "127.0.0.1".to_string(),
listen_port: port,
seed: None,
mnemonic: Some(mnemonic),
signatory_url: None,
signatory_certs: None,

View File

@@ -8,6 +8,7 @@ use std::{env, fs};
use anyhow::{anyhow, bail, Result};
use async_trait::async_trait;
use bip39::Mnemonic;
use cashu::quote_id::QuoteId;
use cashu::{MeltQuoteBolt12Request, MintQuoteBolt12Request, MintQuoteBolt12Response};
use cdk::amount::SplitTarget;
use cdk::cdk_database::{self, MintDatabase, WalletDatabase};
@@ -80,16 +81,15 @@ impl MintConnector for DirectMintConnection {
&self,
quote_id: &str,
) -> Result<MintQuoteBolt11Response<String>, Error> {
let quote_id_uuid = Uuid::from_str(quote_id).unwrap();
self.mint
.check_mint_quote(&quote_id_uuid)
.check_mint_quote(&QuoteId::from_str(quote_id)?)
.await
.map(Into::into)
}
async fn post_mint(&self, request: MintRequest<String>) -> Result<MintResponse, Error> {
let request_uuid = request.try_into().unwrap();
self.mint.process_mint_request(request_uuid).await
let request_id: MintRequest<QuoteId> = request.try_into().unwrap();
self.mint.process_mint_request(request_id).await
}
async fn post_melt_quote(
@@ -106,9 +106,8 @@ impl MintConnector for DirectMintConnection {
&self,
quote_id: &str,
) -> Result<MeltQuoteBolt11Response<String>, Error> {
let quote_id_uuid = Uuid::from_str(quote_id).unwrap();
self.mint
.check_melt_quote(&quote_id_uuid)
.check_melt_quote(&QuoteId::from_str(quote_id)?)
.await
.map(Into::into)
}
@@ -156,7 +155,7 @@ impl MintConnector for DirectMintConnection {
&self,
request: MintQuoteBolt12Request,
) -> Result<MintQuoteBolt12Response<String>, Error> {
let res: MintQuoteBolt12Response<Uuid> =
let res: MintQuoteBolt12Response<QuoteId> =
self.mint.get_mint_quote(request.into()).await?.try_into()?;
Ok(res.into())
}
@@ -165,10 +164,9 @@ impl MintConnector for DirectMintConnection {
&self,
quote_id: &str,
) -> Result<MintQuoteBolt12Response<String>, Error> {
let quote_id_uuid = Uuid::from_str(quote_id).unwrap();
let quote: MintQuoteBolt12Response<Uuid> = self
let quote: MintQuoteBolt12Response<QuoteId> = self
.mint
.check_mint_quote(&quote_id_uuid)
.check_mint_quote(&QuoteId::from_str(quote_id)?)
.await?
.try_into()?;
@@ -190,9 +188,8 @@ impl MintConnector for DirectMintConnection {
&self,
quote_id: &str,
) -> Result<MeltQuoteBolt11Response<String>, Error> {
let quote_id_uuid = Uuid::from_str(quote_id).unwrap();
self.mint
.check_melt_quote(&quote_id_uuid)
.check_melt_quote(&QuoteId::from_str(quote_id)?)
.await
.map(Into::into)
}

View File

@@ -183,6 +183,7 @@ pub fn create_fake_wallet_settings(
url: format!("http://127.0.0.1:{port}"),
listen_host: "127.0.0.1".to_string(),
listen_port: port,
seed: None,
mnemonic,
signatory_url: signatory_config.as_ref().map(|(url, _)| url.clone()),
signatory_certs: signatory_config
@@ -233,6 +234,7 @@ pub fn create_cln_settings(
url: format!("http://127.0.0.1:{port}"),
listen_host: "127.0.0.1".to_string(),
listen_port: port,
seed: None,
mnemonic: Some(mnemonic),
signatory_url: None,
signatory_certs: None,
@@ -277,6 +279,7 @@ pub fn create_lnd_settings(
url: format!("http://127.0.0.1:{port}"),
listen_host: "127.0.0.1".to_string(),
listen_port: port,
seed: None,
mnemonic: Some(mnemonic),
signatory_url: None,
signatory_certs: None,

View File

@@ -677,7 +677,7 @@ impl CdkMint for MintRPCServer {
_ => {
// Create a new quote with the same values
let quote = MintQuote::new(
Some(mint_quote.id), // id
Some(mint_quote.id.clone()), // id
mint_quote.request.clone(), // request
mint_quote.unit.clone(), // unit
mint_quote.amount, // amount

View File

@@ -50,6 +50,8 @@ pub struct Info {
pub url: String,
pub listen_host: String,
pub listen_port: u16,
/// Overrides mnemonic
pub seed: Option<String>,
pub mnemonic: Option<String>,
pub signatory_url: Option<String>,
pub signatory_certs: Option<String>,
@@ -74,6 +76,7 @@ impl Default for Info {
url: String::new(),
listen_host: "127.0.0.1".to_string(),
listen_port: 8091, // Default to port 8091 instead of 0
seed: None,
mnemonic: None,
signatory_url: None,
signatory_certs: None,
@@ -88,7 +91,7 @@ impl Default for Info {
impl std::fmt::Debug for Info {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Use a fallback approach that won't panic
let mnemonic_display = {
let mnemonic_display: String = {
if let Some(mnemonic) = self.mnemonic.as_ref() {
let hash = sha256::Hash::hash(mnemonic.as_bytes());
format!("<hashed: {hash}>")

View File

@@ -6,6 +6,7 @@ pub const DATABASE_URL_ENV_VAR: &str = "CDK_MINTD_DATABASE_URL"; // Legacy, main
pub const ENV_URL: &str = "CDK_MINTD_URL";
pub const ENV_LISTEN_HOST: &str = "CDK_MINTD_LISTEN_HOST";
pub const ENV_LISTEN_PORT: &str = "CDK_MINTD_LISTEN_PORT";
pub const ENV_SEED: &str = "CDK_MINTD_SEED";
pub const ENV_MNEMONIC: &str = "CDK_MINTD_MNEMONIC";
pub const ENV_SIGNATORY_URL: &str = "CDK_MINTD_SIGNATORY_URL";
pub const ENV_SIGNATORY_CERTS: &str = "CDK_MINTD_SIGNATORY_CERTS";

View File

@@ -31,6 +31,10 @@ impl Info {
self.signatory_certs = Some(signatory_certs);
}
if let Ok(seed) = env::var(ENV_SEED) {
self.seed = Some(seed);
}
if let Ok(mnemonic) = env::var(ENV_MNEMONIC) {
self.mnemonic = Some(mnemonic);
}

View File

@@ -805,6 +805,9 @@ async fn build_mint(
.await?,
))
.await?)
} else if let Some(seed) = settings.info.seed.clone() {
let seed_bytes: Vec<u8> = seed.into();
Ok(mint_builder.build_with_seed(keystore, &seed_bytes).await?)
} else if let Some(mnemonic) = settings
.info
.mnemonic

View File

@@ -26,6 +26,7 @@ use cdk_common::mint::{
};
use cdk_common::nut00::ProofsMethods;
use cdk_common::payment::PaymentIdentifier;
use cdk_common::quote_id::QuoteId;
use cdk_common::secret::Secret;
use cdk_common::state::check_state_transition;
use cdk_common::util::unix_time;
@@ -309,7 +310,7 @@ where
#[inline(always)]
async fn get_mint_quote_payments<C>(
conn: &C,
quote_id: &Uuid,
quote_id: &QuoteId,
) -> Result<Vec<IncomingPayment>, Error>
where
C: DatabaseExecutor + Send + Sync,
@@ -327,7 +328,13 @@ where
quote_id=:quote_id
"#,
)?
.bind("quote_id", quote_id.as_hyphenated().to_string())
.bind(
"quote_id",
match quote_id {
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
QuoteId::BASE64(s) => s.to_string(),
},
)
.fetch_all(conn)
.await?
.into_iter()
@@ -344,7 +351,7 @@ where
}
#[inline(always)]
async fn get_mint_quote_issuance<C>(conn: &C, quote_id: &Uuid) -> Result<Vec<Issuance>, Error>
async fn get_mint_quote_issuance<C>(conn: &C, quote_id: &QuoteId) -> Result<Vec<Issuance>, Error>
where
C: DatabaseExecutor + Send + Sync,
{
@@ -356,7 +363,13 @@ FROM mint_quote_issued
WHERE quote_id=:quote_id
"#,
)?
.bind("quote_id", quote_id.as_hyphenated().to_string())
.bind(
"quote_id",
match quote_id {
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
QuoteId::BASE64(s) => s.to_string(),
},
)
.fetch_all(conn)
.await?
.into_iter()
@@ -542,7 +555,7 @@ where
#[instrument(skip(self))]
async fn increment_mint_quote_amount_paid(
&mut self,
quote_id: &Uuid,
quote_id: &QuoteId,
amount_paid: Amount,
payment_id: String,
) -> Result<Amount, Self::Err> {
@@ -578,7 +591,13 @@ where
FOR UPDATE
"#,
)?
.bind("quote_id", quote_id.as_hyphenated().to_string())
.bind(
"quote_id",
match quote_id {
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
QuoteId::BASE64(s) => s.to_string(),
},
)
.fetch_one(&self.inner)
.await
.inspect_err(|err| {
@@ -613,7 +632,13 @@ where
"#,
)?
.bind("amount_paid", new_amount_paid.to_i64())
.bind("quote_id", quote_id.as_hyphenated().to_string())
.bind(
"quote_id",
match quote_id {
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
QuoteId::BASE64(s) => s.to_string(),
},
)
.execute(&self.inner)
.await
.inspect_err(|err| {
@@ -628,7 +653,13 @@ where
VALUES (:quote_id, :payment_id, :amount, :timestamp)
"#,
)?
.bind("quote_id", quote_id.as_hyphenated().to_string())
.bind(
"quote_id",
match quote_id {
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
QuoteId::BASE64(s) => s.to_string(),
},
)
.bind("payment_id", payment_id)
.bind("amount", amount_paid.to_i64())
.bind("timestamp", unix_time() as i64)
@@ -645,7 +676,7 @@ where
#[instrument(skip_all)]
async fn increment_mint_quote_amount_issued(
&mut self,
quote_id: &Uuid,
quote_id: &QuoteId,
amount_issued: Amount,
) -> Result<Amount, Self::Err> {
// Get current amount_issued from quote
@@ -657,7 +688,13 @@ where
FOR UPDATE
"#,
)?
.bind("quote_id", quote_id.as_hyphenated().to_string())
.bind(
"quote_id",
match quote_id {
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
QuoteId::BASE64(s) => s.to_string(),
},
)
.fetch_one(&self.inner)
.await
.inspect_err(|err| {
@@ -685,7 +722,13 @@ where
"#,
)?
.bind("amount_issued", new_amount_issued.to_i64())
.bind("quote_id", quote_id.as_hyphenated().to_string())
.bind(
"quote_id",
match quote_id {
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
QuoteId::BASE64(s) => s.to_string(),
},
)
.execute(&self.inner)
.await
.inspect_err(|err| {
@@ -701,7 +744,13 @@ INSERT INTO mint_quote_issued
VALUES (:quote_id, :amount, :timestamp);
"#,
)?
.bind("quote_id", quote_id.as_hyphenated().to_string())
.bind(
"quote_id",
match quote_id {
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
QuoteId::BASE64(s) => s.to_string(),
},
)
.bind("amount", amount_issued.to_i64())
.bind("timestamp", current_time as i64)
.execute(&self.inner)
@@ -741,9 +790,15 @@ VALUES (:quote_id, :amount, :timestamp);
Ok(())
}
async fn remove_mint_quote(&mut self, quote_id: &Uuid) -> Result<(), Self::Err> {
async fn remove_mint_quote(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err> {
query(r#"DELETE FROM mint_quote WHERE id=:id"#)?
.bind("id", quote_id.as_hyphenated().to_string())
.bind(
"id",
match quote_id {
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
QuoteId::BASE64(s) => s.to_string(),
},
)
.execute(&self.inner)
.await?;
Ok(())
@@ -800,13 +855,16 @@ VALUES (:quote_id, :amount, :timestamp);
async fn update_melt_quote_request_lookup_id(
&mut self,
quote_id: &Uuid,
quote_id: &QuoteId,
new_request_lookup_id: &PaymentIdentifier,
) -> Result<(), Self::Err> {
query(r#"UPDATE melt_quote SET request_lookup_id = :new_req_id, request_lookup_id_kind = :new_kind WHERE id = :id"#)?
.bind("new_req_id", new_request_lookup_id.to_string())
.bind("new_kind",new_request_lookup_id.kind() )
.bind("id", quote_id.as_hyphenated().to_string())
.bind("id", match quote_id {
QuoteId::BASE64(s) => s.to_string(),
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
})
.execute(&self.inner)
.await?;
Ok(())
@@ -814,7 +872,7 @@ VALUES (:quote_id, :amount, :timestamp);
async fn update_melt_quote_state(
&mut self,
quote_id: &Uuid,
quote_id: &QuoteId,
state: MeltQuoteState,
payment_proof: Option<String>,
) -> Result<(MeltQuoteState, mint::MeltQuote), Self::Err> {
@@ -842,7 +900,13 @@ VALUES (:quote_id, :amount, :timestamp);
AND state != :state
"#,
)?
.bind("id", quote_id.as_hyphenated().to_string())
.bind(
"id",
match quote_id {
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
QuoteId::BASE64(s) => s.to_string(),
},
)
.bind("state", state.to_string())
.fetch_one(&self.inner)
.await?
@@ -856,13 +920,22 @@ VALUES (:quote_id, :amount, :timestamp);
.bind("state", state.to_string())
.bind("paid_time", current_time as i64)
.bind("payment_preimage", payment_proof)
.bind("id", quote_id.as_hyphenated().to_string())
.bind("id", match quote_id {
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
QuoteId::BASE64(s) => s.to_string(),
})
.execute(&self.inner)
.await
} else {
query(r#"UPDATE melt_quote SET state = :state WHERE id = :id"#)?
.bind("state", state.to_string())
.bind("id", quote_id.as_hyphenated().to_string())
.bind(
"id",
match quote_id {
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
QuoteId::BASE64(s) => s.to_string(),
},
)
.execute(&self.inner)
.await
};
@@ -895,7 +968,7 @@ VALUES (:quote_id, :amount, :timestamp);
Ok(())
}
async fn get_mint_quote(&mut self, quote_id: &Uuid) -> Result<Option<MintQuote>, Self::Err> {
async fn get_mint_quote(&mut self, quote_id: &QuoteId) -> Result<Option<MintQuote>, Self::Err> {
let payments = get_mint_quote_payments(&self.inner, quote_id).await?;
let issuance = get_mint_quote_issuance(&self.inner, quote_id).await?;
@@ -920,7 +993,13 @@ VALUES (:quote_id, :amount, :timestamp);
FOR UPDATE
"#,
)?
.bind("id", quote_id.as_hyphenated().to_string())
.bind(
"id",
match quote_id {
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
QuoteId::BASE64(s) => s.to_string(),
},
)
.fetch_one(&self.inner)
.await?
.map(|row| sql_row_to_mint_quote(row, payments, issuance))
@@ -1053,7 +1132,7 @@ where
{
type Err = Error;
async fn get_mint_quote(&self, quote_id: &Uuid) -> Result<Option<MintQuote>, Self::Err> {
async fn get_mint_quote(&self, quote_id: &QuoteId) -> Result<Option<MintQuote>, Self::Err> {
let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
let payments = get_mint_quote_payments(&*conn, quote_id).await?;
@@ -1078,7 +1157,13 @@ where
mint_quote
WHERE id = :id"#,
)?
.bind("id", quote_id.as_hyphenated().to_string())
.bind(
"id",
match quote_id {
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
QuoteId::BASE64(s) => s.to_string(),
},
)
.fetch_one(&*conn)
.await?
.map(|row| sql_row_to_mint_quote(row, payments, issuance))
@@ -1206,7 +1291,10 @@ where
Ok(mint_quotes)
}
async fn get_melt_quote(&self, quote_id: &Uuid) -> Result<Option<mint::MeltQuote>, Self::Err> {
async fn get_melt_quote(
&self,
quote_id: &QuoteId,
) -> Result<Option<mint::MeltQuote>, Self::Err> {
let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
Ok(query(
r#"
@@ -1231,7 +1319,13 @@ where
id=:id
"#,
)?
.bind("id", quote_id.as_hyphenated().to_string())
.bind(
"id",
match quote_id {
QuoteId::BASE64(s) => s.to_string(),
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
},
)
.fetch_one(&*conn)
.await?
.map(sql_row_to_melt_quote)
@@ -1386,7 +1480,7 @@ where
&mut self,
blinded_messages: &[PublicKey],
blind_signatures: &[BlindSignature],
quote_id: Option<Uuid>,
quote_id: Option<QuoteId>,
) -> Result<(), Self::Err> {
let current_time = unix_time();
@@ -1403,7 +1497,10 @@ where
.bind("amount", u64::from(signature.amount) as i64)
.bind("keyset_id", signature.keyset_id.to_string())
.bind("c", signature.c.to_bytes().to_vec())
.bind("quote_id", quote_id.map(|q| q.hyphenated().to_string()))
.bind("quote_id", quote_id.as_ref().map(|q| match q {
QuoteId::BASE64(s) => s.to_string(),
QuoteId::UUID(u) => u.hyphenated().to_string(),
}))
.bind(
"dleq_e",
signature.dleq.as_ref().map(|dleq| dleq.e.to_secret_hex()),
@@ -1547,7 +1644,7 @@ where
/// Get [`BlindSignature`]s for quote
async fn get_blind_signatures_for_quote(
&self,
quote_id: &Uuid,
quote_id: &QuoteId,
) -> Result<Vec<BlindSignature>, Self::Err> {
let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
Ok(query(
@@ -1564,7 +1661,13 @@ where
quote_id=:quote_id
"#,
)?
.bind("quote_id", quote_id.to_string())
.bind(
"quote_id",
match quote_id {
QuoteId::BASE64(s) => s.to_string(),
QuoteId::UUID(u) => u.as_hyphenated().to_string(),
},
)
.fetch_all(&*conn)
.await?
.into_iter()
@@ -1658,7 +1761,7 @@ fn sql_row_to_mint_quote(
let payment_method = column_as_string!(payment_method, PaymentMethod::from_str);
Ok(MintQuote::new(
Some(Uuid::parse_str(&id).map_err(|_| Error::InvalidUuid(id))?),
Some(QuoteId::from_str(&id)?),
request_str,
column_as_string!(unit, CurrencyUnit::from_str),
amount.map(Amount::from),
@@ -1745,7 +1848,7 @@ fn sql_row_to_melt_quote(row: Vec<Column>) -> Result<mint::MeltQuote, Error> {
};
Ok(MeltQuote {
id: Uuid::parse_str(&id).map_err(|_| Error::InvalidUuid(id))?,
id: QuoteId::from_str(&id)?,
unit: CurrencyUnit::from_str(&unit)?,
amount: Amount::from(amount),
request,

View File

@@ -3,6 +3,7 @@ use cdk_common::payment::{
Bolt11IncomingPaymentOptions, Bolt11Settings, Bolt12IncomingPaymentOptions,
IncomingPaymentOptions, WaitPaymentResponse,
};
use cdk_common::quote_id::QuoteId;
use cdk_common::util::unix_time;
use cdk_common::{
database, ensure_cdk, Amount, CurrencyUnit, Error, MintQuoteBolt11Request,
@@ -10,7 +11,6 @@ use cdk_common::{
MintRequest, MintResponse, NotificationPayload, PaymentMethod, PublicKey,
};
use tracing::instrument;
use uuid::Uuid;
use crate::mint::Verification;
use crate::Mint;
@@ -49,12 +49,12 @@ impl From<MintQuoteBolt12Request> for MintQuoteRequest {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MintQuoteResponse {
/// Lightning Network BOLT11 invoice response
Bolt11(MintQuoteBolt11Response<Uuid>),
Bolt11(MintQuoteBolt11Response<QuoteId>),
/// Lightning Network BOLT12 offer response
Bolt12(MintQuoteBolt12Response<Uuid>),
Bolt12(MintQuoteBolt12Response<QuoteId>),
}
impl TryFrom<MintQuoteResponse> for MintQuoteBolt11Response<Uuid> {
impl TryFrom<MintQuoteResponse> for MintQuoteBolt11Response<QuoteId> {
type Error = Error;
fn try_from(response: MintQuoteResponse) -> Result<Self, Self::Error> {
@@ -65,7 +65,7 @@ impl TryFrom<MintQuoteResponse> for MintQuoteBolt11Response<Uuid> {
}
}
impl TryFrom<MintQuoteResponse> for MintQuoteBolt12Response<Uuid> {
impl TryFrom<MintQuoteResponse> for MintQuoteBolt12Response<QuoteId> {
type Error = Error;
fn try_from(response: MintQuoteResponse) -> Result<Self, Self::Error> {
@@ -82,7 +82,7 @@ impl TryFrom<MintQuote> for MintQuoteResponse {
fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
match quote.payment_method {
PaymentMethod::Bolt11 => {
let bolt11_response: MintQuoteBolt11Response<Uuid> = quote.into();
let bolt11_response: MintQuoteBolt11Response<QuoteId> = quote.into();
Ok(MintQuoteResponse::Bolt11(bolt11_response))
}
PaymentMethod::Bolt12 => {
@@ -298,12 +298,12 @@ impl Mint {
match payment_method {
PaymentMethod::Bolt11 => {
let res: MintQuoteBolt11Response<Uuid> = quote.clone().into();
let res: MintQuoteBolt11Response<QuoteId> = quote.clone().into();
self.pubsub_manager
.broadcast(NotificationPayload::MintQuoteBolt11Response(res));
}
PaymentMethod::Bolt12 => {
let res: MintQuoteBolt12Response<Uuid> = quote.clone().try_into()?;
let res: MintQuoteBolt12Response<QuoteId> = quote.clone().try_into()?;
self.pubsub_manager
.broadcast(NotificationPayload::MintQuoteBolt12Response(res));
}
@@ -333,7 +333,7 @@ impl Mint {
/// * `Ok(())` if removal was successful
/// * `Error` if the quote doesn't exist or removal fails
#[instrument(skip_all)]
pub async fn remove_mint_quote(&self, quote_id: &Uuid) -> Result<(), Error> {
pub async fn remove_mint_quote(&self, quote_id: &QuoteId) -> Result<(), Error> {
let mut tx = self.localstore.begin_transaction().await?;
tx.remove_mint_quote(quote_id).await?;
tx.commit().await?;
@@ -421,7 +421,7 @@ impl Mint {
/// * `MintQuoteResponse` - The current state of the quote
/// * `Error` if the quote doesn't exist or checking fails
#[instrument(skip(self))]
pub async fn check_mint_quote(&self, quote_id: &Uuid) -> Result<MintQuoteResponse, Error> {
pub async fn check_mint_quote(&self, quote_id: &QuoteId) -> Result<MintQuoteResponse, Error> {
let mut quote = self
.localstore
.get_mint_quote(quote_id)
@@ -454,7 +454,7 @@ impl Mint {
#[instrument(skip_all)]
pub async fn process_mint_request(
&self,
mint_request: MintRequest<Uuid>,
mint_request: MintRequest<QuoteId>,
) -> Result<MintResponse, Error> {
let mut mint_quote = self
.localstore
@@ -563,7 +563,7 @@ impl Mint {
.map(|p| p.blinded_secret)
.collect::<Vec<PublicKey>>(),
&blind_signatures,
Some(mint_request.quote),
Some(mint_request.quote.clone()),
)
.await?;

View File

@@ -11,10 +11,10 @@ use cdk_common::payment::{
Bolt11OutgoingPaymentOptions, Bolt12OutgoingPaymentOptions, OutgoingPaymentOptions,
PaymentIdentifier,
};
use cdk_common::quote_id::QuoteId;
use cdk_common::{MeltOptions, MeltQuoteBolt12Request};
use lightning::offers::offer::Offer;
use tracing::instrument;
use uuid::Uuid;
use super::{
CurrencyUnit, MeltQuote, MeltQuoteBolt11Request, MeltQuoteBolt11Response, MeltRequest, Mint,
@@ -114,7 +114,7 @@ impl Mint {
pub async fn get_melt_quote(
&self,
melt_quote_request: MeltQuoteRequest,
) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
) -> Result<MeltQuoteBolt11Response<QuoteId>, Error> {
match melt_quote_request {
MeltQuoteRequest::Bolt11(bolt11_request) => {
self.get_melt_bolt11_quote_impl(&bolt11_request).await
@@ -130,7 +130,7 @@ impl Mint {
async fn get_melt_bolt11_quote_impl(
&self,
melt_request: &MeltQuoteBolt11Request,
) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
) -> Result<MeltQuoteBolt11Response<QuoteId>, Error> {
let MeltQuoteBolt11Request {
request,
unit,
@@ -222,7 +222,7 @@ impl Mint {
async fn get_melt_bolt12_quote_impl(
&self,
melt_request: &MeltQuoteBolt12Request,
) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
) -> Result<MeltQuoteBolt11Response<QuoteId>, Error> {
let MeltQuoteBolt12Request {
request,
unit,
@@ -322,8 +322,8 @@ impl Mint {
#[instrument(skip(self))]
pub async fn check_melt_quote(
&self,
quote_id: &Uuid,
) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
quote_id: &QuoteId,
) -> Result<MeltQuoteBolt11Response<QuoteId>, Error> {
let quote = self
.localstore
.get_melt_quote(quote_id)
@@ -363,7 +363,7 @@ impl Mint {
pub async fn check_melt_expected_ln_fees(
&self,
melt_quote: &MeltQuote,
melt_request: &MeltRequest<Uuid>,
melt_request: &MeltRequest<QuoteId>,
) -> Result<Option<Amount>, Error> {
let quote_msats = to_unit(melt_quote.amount, &melt_quote.unit, &CurrencyUnit::Msat)
.expect("Quote unit is checked above that it can convert to msat");
@@ -445,7 +445,7 @@ impl Mint {
&self,
tx: &mut Box<dyn MintTransaction<'_, database::Error> + Send + Sync + '_>,
input_verification: Verification,
melt_request: &MeltRequest<Uuid>,
melt_request: &MeltRequest<QuoteId>,
) -> Result<(ProofWriter, MeltQuote), Error> {
let (state, quote) = tx
.update_melt_quote_state(melt_request.quote(), MeltQuoteState::Pending, None)
@@ -518,8 +518,8 @@ impl Mint {
#[instrument(skip_all)]
pub async fn melt(
&self,
melt_request: &MeltRequest<Uuid>,
) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
melt_request: &MeltRequest<QuoteId>,
) -> Result<MeltQuoteBolt11Response<QuoteId>, Error> {
use std::sync::Arc;
async fn check_payment_state(
ln: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
@@ -743,10 +743,10 @@ impl Mint {
mut tx: Box<dyn MintTransaction<'_, database::Error> + Send + Sync + '_>,
mut proof_writer: ProofWriter,
quote: MeltQuote,
melt_request: &MeltRequest<Uuid>,
melt_request: &MeltRequest<QuoteId>,
payment_preimage: Option<String>,
total_spent: Amount,
) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
) -> Result<MeltQuoteBolt11Response<QuoteId>, Error> {
let input_ys = melt_request.inputs().ys()?;
proof_writer
@@ -823,7 +823,7 @@ impl Mint {
.map(|o| o.blinded_secret)
.collect::<Vec<PublicKey>>(),
&change_sigs,
Some(quote.id),
Some(quote.id.clone()),
)
.await?;

View File

@@ -12,6 +12,7 @@ use cdk_common::database::MintAuthDatabase;
use cdk_common::database::{self, MintDatabase, MintTransaction};
use cdk_common::nuts::{self, BlindSignature, BlindedMessage, CurrencyUnit, Id, Kind};
use cdk_common::payment::WaitPaymentResponse;
pub use cdk_common::quote_id::QuoteId;
use cdk_common::secret;
use cdk_signatory::signatory::{Signatory, SignatoryKeySet};
use futures::StreamExt;
@@ -21,7 +22,6 @@ use subscription::PubSubManager;
use tokio::sync::{Mutex, Notify};
use tokio::task::{JoinHandle, JoinSet};
use tracing::instrument;
use uuid::Uuid;
use crate::cdk_payment::{self, MintPayment};
use crate::error::Error;
@@ -758,7 +758,7 @@ impl Mint {
&self,
tx: &mut Box<dyn MintTransaction<'_, cdk_database::Error> + Send + Sync + '_>,
melt_quote: &MeltQuote,
melt_request: &MeltRequest<Uuid>,
melt_request: &MeltRequest<QuoteId>,
) -> Result<Option<Amount>, Error> {
let mint_quote = match tx
.get_mint_quote_by_request(&melt_quote.request.to_string())

View File

@@ -5,8 +5,8 @@ use std::sync::Arc;
use cdk_common::database::{self, MintDatabase};
use cdk_common::mint::MintQuote;
use cdk_common::nut17::Notification;
use cdk_common::quote_id::QuoteId;
use cdk_common::{Amount, MintQuoteBolt12Response, NotificationPayload, PaymentMethod};
use uuid::Uuid;
use super::OnSubscription;
use crate::nuts::{
@@ -20,7 +20,9 @@ use crate::pub_sub;
///
/// Nut-17 implementation is system-wide and not only through the WebSocket, so
/// it is possible for another part of the system to subscribe to events.
pub struct PubSubManager(pub_sub::Manager<NotificationPayload<Uuid>, Notification, OnSubscription>);
pub struct PubSubManager(
pub_sub::Manager<NotificationPayload<QuoteId>, Notification, OnSubscription>,
);
#[allow(clippy::default_constructed_unit_structs)]
impl Default for PubSubManager {
@@ -36,7 +38,7 @@ impl From<Arc<dyn MintDatabase<database::Error> + Send + Sync>> for PubSubManage
}
impl Deref for PubSubManager {
type Target = pub_sub::Manager<NotificationPayload<Uuid>, Notification, OnSubscription>;
type Target = pub_sub::Manager<NotificationPayload<QuoteId>, Notification, OnSubscription>;
fn deref(&self) -> &Self::Target {
&self.0
@@ -88,7 +90,7 @@ impl PubSubManager {
}
/// Helper function to emit a MintQuoteBolt11Response status
pub fn mint_quote_bolt11_status<E: Into<MintQuoteBolt11Response<Uuid>>>(
pub fn mint_quote_bolt11_status<E: Into<MintQuoteBolt11Response<QuoteId>>>(
&self,
quote: E,
new_state: MintQuoteState,
@@ -100,7 +102,7 @@ impl PubSubManager {
}
/// Helper function to emit a MintQuoteBolt11Response status
pub fn mint_quote_bolt12_status<E: TryInto<MintQuoteBolt12Response<Uuid>>>(
pub fn mint_quote_bolt12_status<E: TryInto<MintQuoteBolt12Response<QuoteId>>>(
&self,
quote: E,
amount_paid: Amount,
@@ -117,7 +119,7 @@ impl PubSubManager {
}
/// Helper function to emit a MeltQuoteBolt11Response status
pub fn melt_quote_status<E: Into<MeltQuoteBolt11Response<Uuid>>>(
pub fn melt_quote_status<E: Into<MeltQuoteBolt11Response<QuoteId>>>(
&self,
quote: E,
payment_preimage: Option<String>,

View File

@@ -6,8 +6,8 @@ use std::sync::Arc;
use cdk_common::database::{self, MintDatabase};
use cdk_common::nut17::Notification;
use cdk_common::pub_sub::OnNewSubscription;
use cdk_common::quote_id::QuoteId;
use cdk_common::{MintQuoteBolt12Response, NotificationPayload, PaymentMethod};
use uuid::Uuid;
use crate::nuts::{MeltQuoteBolt11Response, MintQuoteBolt11Response, ProofState, PublicKey};
@@ -21,7 +21,7 @@ pub struct OnSubscription(pub(crate) Option<Arc<dyn MintDatabase<database::Error
#[async_trait::async_trait]
impl OnNewSubscription for OnSubscription {
type Event = NotificationPayload<Uuid>;
type Event = NotificationPayload<QuoteId>;
type Index = Notification;
async fn on_new_subscription(
@@ -65,7 +65,7 @@ impl OnNewSubscription for OnSubscription {
quotes
.into_iter()
.filter_map(|quote| quote.map(|x| x.into()))
.map(|x: MeltQuoteBolt11Response<Uuid>| x.into())
.map(|x: MeltQuoteBolt11Response<QuoteId>| x.into())
.collect::<Vec<_>>()
})
.map_err(|e| e.to_string())?,
@@ -82,12 +82,13 @@ impl OnNewSubscription for OnSubscription {
.filter_map(|quote| {
quote.and_then(|x| match x.payment_method {
PaymentMethod::Bolt11 => {
let response: MintQuoteBolt11Response<Uuid> = x.into();
let response: MintQuoteBolt11Response<QuoteId> = x.into();
Some(response.into())
}
PaymentMethod::Bolt12 => match x.try_into() {
Ok(response) => {
let response: MintQuoteBolt12Response<Uuid> = response;
let response: MintQuoteBolt12Response<QuoteId> =
response;
Some(response.into())
}
Err(_) => None,