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] [dev-dependencies]
bip39.workspace = true 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::ser::{SerializeStruct, Serializer};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
#[cfg(feature = "mint")]
use uuid::Uuid;
use super::nut00::{BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod}; 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; use crate::Amount;
/// NUT04 Error /// NUT04 Error
@@ -44,12 +46,12 @@ pub struct MintRequest<Q> {
} }
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
impl TryFrom<MintRequest<String>> for MintRequest<Uuid> { impl TryFrom<MintRequest<String>> for MintRequest<QuoteId> {
type Error = uuid::Error; type Error = QuoteIdError;
fn try_from(value: MintRequest<String>) -> Result<Self, Self::Error> { fn try_from(value: MintRequest<String>) -> Result<Self, Self::Error> {
Ok(Self { Ok(Self {
quote: Uuid::from_str(&value.quote)?, quote: QuoteId::from_str(&value.quote)?,
outputs: value.outputs, outputs: value.outputs,
signature: value.signature, signature: value.signature,
}) })

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -126,6 +126,13 @@ pub enum Error {
#[error(transparent)] #[error(transparent)]
#[cfg(feature = "auth")] #[cfg(feature = "auth")]
NUT22(#[from] crate::nuts::nut22::Error), 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 /// Serde Error
#[error(transparent)] #[error(transparent)]
Serde(#[from] serde_json::Error), Serde(#[from] serde_json::Error),

View File

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

View File

@@ -29,5 +29,7 @@ pub use bitcoin;
pub use cashu::amount::{self, Amount}; pub use cashu::amount::{self, Amount};
pub use cashu::lightning_invoice::{self, Bolt11Invoice}; pub use cashu::lightning_invoice::{self, Bolt11Invoice};
pub use cashu::nuts::{self, *}; 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 cashu::{dhke, ensure_cdk, mint_url, secret, util, SECP256K1};
pub use error::Error; pub use error::Error;

View File

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

View File

@@ -6,11 +6,11 @@ use cashu::nut17::{self};
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
use cashu::nut17::{Error, Kind, Notification}; use cashu::nut17::{Error, Kind, Notification};
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
use cashu::quote_id::QuoteId;
#[cfg(feature = "mint")]
use cashu::{NotificationPayload, PublicKey}; use cashu::{NotificationPayload, PublicKey};
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "mint")]
use uuid::Uuid;
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
use crate::pub_sub::index::{Index, Indexable, SubscriptionGlobalId}; use crate::pub_sub::index::{Index, Indexable, SubscriptionGlobalId};
@@ -45,14 +45,14 @@ impl TryFrom<IndexableParams> for Vec<Index<Notification>> {
.map(|filter| { .map(|filter| {
let idx = match params.kind { let idx = match params.kind {
Kind::Bolt11MeltQuote => { Kind::Bolt11MeltQuote => {
Notification::MeltQuoteBolt11(Uuid::from_str(&filter)?) Notification::MeltQuoteBolt11(QuoteId::from_str(&filter)?)
} }
Kind::Bolt11MintQuote => { Kind::Bolt11MintQuote => {
Notification::MintQuoteBolt11(Uuid::from_str(&filter)?) Notification::MintQuoteBolt11(QuoteId::from_str(&filter)?)
} }
Kind::ProofState => Notification::ProofState(PublicKey::from_str(&filter)?), Kind::ProofState => Notification::ProofState(PublicKey::from_str(&filter)?),
Kind::Bolt12MintQuote => { 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")] #[cfg(feature = "mint")]
impl Indexable for NotificationPayload<Uuid> { impl Indexable for NotificationPayload<QuoteId> {
type Type = Notification; type Type = Notification;
fn to_indexes(&self) -> Vec<Index<Self::Type>> { 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))] vec![Index::from(Notification::ProofState(proof_state.y))]
} }
NotificationPayload::MeltQuoteBolt11Response(melt_quote) => { 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) => { 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) => { 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::ws::JSON_RPC_VERSION;
use cashu::nut17::{self}; use cashu::nut17::{self};
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
use cashu::NotificationPayload; use cashu::quote_id::QuoteId;
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
use uuid::Uuid; use cashu::NotificationPayload;
use crate::pub_sub::SubId; use crate::pub_sub::SubId;
@@ -48,7 +48,7 @@ pub type NotificationInner<T> = nut17::ws::NotificationInner<T, SubId>;
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
/// Converts a notification with UUID identifiers to a notification with string identifiers /// Converts a notification with UUID identifiers to a notification with string identifiers
pub fn notification_uuid_to_notification_string( pub fn notification_uuid_to_notification_string(
notification: NotificationInner<Uuid>, notification: NotificationInner<QuoteId>,
) -> NotificationInner<String> { ) -> NotificationInner<String> {
nut17::ws::NotificationInner { nut17::ws::NotificationInner {
sub_id: notification.sub_id, sub_id: notification.sub_id,
@@ -69,7 +69,7 @@ pub fn notification_uuid_to_notification_string(
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
/// Converts a notification to a websocket message that can be sent to clients /// 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 { nut17::ws::WsMessageOrResponse::Notification(nut17::ws::WsNotification {
jsonrpc: JSON_RPC_VERSION.to_owned(), jsonrpc: JSON_RPC_VERSION.to_owned(),
method: "subscribe".to_string(), method: "subscribe".to_string(),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,6 +31,10 @@ impl Info {
self.signatory_certs = Some(signatory_certs); 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) { if let Ok(mnemonic) = env::var(ENV_MNEMONIC) {
self.mnemonic = Some(mnemonic); self.mnemonic = Some(mnemonic);
} }

View File

@@ -805,6 +805,9 @@ async fn build_mint(
.await?, .await?,
)) ))
.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 } else if let Some(mnemonic) = settings
.info .info
.mnemonic .mnemonic

View File

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

View File

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

View File

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

View File

@@ -12,6 +12,7 @@ use cdk_common::database::MintAuthDatabase;
use cdk_common::database::{self, MintDatabase, MintTransaction}; use cdk_common::database::{self, MintDatabase, MintTransaction};
use cdk_common::nuts::{self, BlindSignature, BlindedMessage, CurrencyUnit, Id, Kind}; use cdk_common::nuts::{self, BlindSignature, BlindedMessage, CurrencyUnit, Id, Kind};
use cdk_common::payment::WaitPaymentResponse; use cdk_common::payment::WaitPaymentResponse;
pub use cdk_common::quote_id::QuoteId;
use cdk_common::secret; use cdk_common::secret;
use cdk_signatory::signatory::{Signatory, SignatoryKeySet}; use cdk_signatory::signatory::{Signatory, SignatoryKeySet};
use futures::StreamExt; use futures::StreamExt;
@@ -21,7 +22,6 @@ use subscription::PubSubManager;
use tokio::sync::{Mutex, Notify}; use tokio::sync::{Mutex, Notify};
use tokio::task::{JoinHandle, JoinSet}; use tokio::task::{JoinHandle, JoinSet};
use tracing::instrument; use tracing::instrument;
use uuid::Uuid;
use crate::cdk_payment::{self, MintPayment}; use crate::cdk_payment::{self, MintPayment};
use crate::error::Error; use crate::error::Error;
@@ -758,7 +758,7 @@ impl Mint {
&self, &self,
tx: &mut Box<dyn MintTransaction<'_, cdk_database::Error> + Send + Sync + '_>, tx: &mut Box<dyn MintTransaction<'_, cdk_database::Error> + Send + Sync + '_>,
melt_quote: &MeltQuote, melt_quote: &MeltQuote,
melt_request: &MeltRequest<Uuid>, melt_request: &MeltRequest<QuoteId>,
) -> Result<Option<Amount>, Error> { ) -> Result<Option<Amount>, Error> {
let mint_quote = match tx let mint_quote = match tx
.get_mint_quote_by_request(&melt_quote.request.to_string()) .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::database::{self, MintDatabase};
use cdk_common::mint::MintQuote; use cdk_common::mint::MintQuote;
use cdk_common::nut17::Notification; use cdk_common::nut17::Notification;
use cdk_common::quote_id::QuoteId;
use cdk_common::{Amount, MintQuoteBolt12Response, NotificationPayload, PaymentMethod}; use cdk_common::{Amount, MintQuoteBolt12Response, NotificationPayload, PaymentMethod};
use uuid::Uuid;
use super::OnSubscription; use super::OnSubscription;
use crate::nuts::{ use crate::nuts::{
@@ -20,7 +20,9 @@ use crate::pub_sub;
/// ///
/// Nut-17 implementation is system-wide and not only through the WebSocket, so /// 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. /// 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)] #[allow(clippy::default_constructed_unit_structs)]
impl Default for PubSubManager { impl Default for PubSubManager {
@@ -36,7 +38,7 @@ impl From<Arc<dyn MintDatabase<database::Error> + Send + Sync>> for PubSubManage
} }
impl Deref for PubSubManager { 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 { fn deref(&self) -> &Self::Target {
&self.0 &self.0
@@ -88,7 +90,7 @@ impl PubSubManager {
} }
/// Helper function to emit a MintQuoteBolt11Response status /// 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, &self,
quote: E, quote: E,
new_state: MintQuoteState, new_state: MintQuoteState,
@@ -100,7 +102,7 @@ impl PubSubManager {
} }
/// Helper function to emit a MintQuoteBolt11Response status /// 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, &self,
quote: E, quote: E,
amount_paid: Amount, amount_paid: Amount,
@@ -117,7 +119,7 @@ impl PubSubManager {
} }
/// Helper function to emit a MeltQuoteBolt11Response status /// 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, &self,
quote: E, quote: E,
payment_preimage: Option<String>, payment_preimage: Option<String>,

View File

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