Forráskód Böngészése

feat: use Uuid as mint and melt quote ids (#469)

Timothée Delabrouille 3 hónapja
szülő
commit
7d87c4806c

+ 1 - 0
crates/cdk-axum/Cargo.toml

@@ -29,6 +29,7 @@ moka = { version = "0.11.1", features = ["future"] }
 serde_json = "1"
 paste = "1.0.15"
 serde = { version = "1.0.210", features = ["derive"] }
+uuid = { version = "1", features = ["v4", "serde"] }
 
 [features]
 swagger = ["cdk/swagger", "dep:utoipa"]

+ 6 - 4
crates/cdk-axum/src/lib.rs

@@ -45,6 +45,8 @@ mod swagger_imports {
 
 #[cfg(feature = "swagger")]
 use swagger_imports::*;
+#[cfg(feature = "swagger")]
+use uuid::Uuid;
 
 /// CDK Mint State
 #[derive(Clone)]
@@ -75,16 +77,16 @@ pub struct MintState {
         KeySet,
         KeySetInfo,
         KeySetVersion,
-        MeltBolt11Request,
+        MeltBolt11Request<Uuid>,
         MeltQuoteBolt11Request,
-        MeltQuoteBolt11Response,
+        MeltQuoteBolt11Response<Uuid>,
         MeltQuoteState,
         MeltMethodSettings,
-        MintBolt11Request,
+        MintBolt11Request<Uuid>,
         MintBolt11Response,
         MintInfo,
         MintQuoteBolt11Request,
-        MintQuoteBolt11Response,
+        MintQuoteBolt11Response<Uuid>,
         MintQuoteState,
         MintMethodSettings,
         MintVersion,

+ 20 - 11
crates/cdk-axum/src/router_handlers.rs

@@ -13,6 +13,7 @@ use cdk::nuts::{
 use cdk::util::unix_time;
 use cdk::Error;
 use paste::paste;
+use uuid::Uuid;
 
 use crate::ws::main_websocket;
 use crate::MintState;
@@ -50,8 +51,16 @@ macro_rules! post_cache_wrapper {
 }
 
 post_cache_wrapper!(post_swap, SwapRequest, SwapResponse);
-post_cache_wrapper!(post_mint_bolt11, MintBolt11Request, MintBolt11Response);
-post_cache_wrapper!(post_melt_bolt11, MeltBolt11Request, MeltQuoteBolt11Response);
+post_cache_wrapper!(
+    post_mint_bolt11,
+    MintBolt11Request<Uuid>,
+    MintBolt11Response
+);
+post_cache_wrapper!(
+    post_melt_bolt11,
+    MeltBolt11Request<Uuid>,
+    MeltQuoteBolt11Response<Uuid>
+);
 
 #[cfg_attr(feature = "swagger", utoipa::path(
     get,
@@ -137,7 +146,7 @@ pub async fn get_keysets(State(state): State<MintState>) -> Result<Json<KeysetRe
 pub async fn post_mint_bolt11_quote(
     State(state): State<MintState>,
     Json(payload): Json<MintQuoteBolt11Request>,
-) -> Result<Json<MintQuoteBolt11Response>, Response> {
+) -> Result<Json<MintQuoteBolt11Response<Uuid>>, Response> {
     let quote = state
         .mint
         .get_mint_bolt11_quote(payload)
@@ -164,8 +173,8 @@ pub async fn post_mint_bolt11_quote(
 /// Get mint quote state.
 pub async fn get_check_mint_bolt11_quote(
     State(state): State<MintState>,
-    Path(quote_id): Path<String>,
-) -> Result<Json<MintQuoteBolt11Response>, Response> {
+    Path(quote_id): Path<Uuid>,
+) -> Result<Json<MintQuoteBolt11Response<Uuid>>, Response> {
     let quote = state
         .mint
         .check_mint_quote(&quote_id)
@@ -199,7 +208,7 @@ pub async fn ws_handler(State(state): State<MintState>, ws: WebSocketUpgrade) ->
 ))]
 pub async fn post_mint_bolt11(
     State(state): State<MintState>,
-    Json(payload): Json<MintBolt11Request>,
+    Json(payload): Json<MintBolt11Request<Uuid>>,
 ) -> Result<Json<MintBolt11Response>, Response> {
     let res = state
         .mint
@@ -227,7 +236,7 @@ pub async fn post_mint_bolt11(
 pub async fn post_melt_bolt11_quote(
     State(state): State<MintState>,
     Json(payload): Json<MeltQuoteBolt11Request>,
-) -> Result<Json<MeltQuoteBolt11Response>, Response> {
+) -> Result<Json<MeltQuoteBolt11Response<Uuid>>, Response> {
     let quote = state
         .mint
         .get_melt_bolt11_quote(&payload)
@@ -254,8 +263,8 @@ pub async fn post_melt_bolt11_quote(
 /// Get melt quote state.
 pub async fn get_check_melt_bolt11_quote(
     State(state): State<MintState>,
-    Path(quote_id): Path<String>,
-) -> Result<Json<MeltQuoteBolt11Response>, Response> {
+    Path(quote_id): Path<Uuid>,
+) -> Result<Json<MeltQuoteBolt11Response<Uuid>>, Response> {
     let quote = state
         .mint
         .check_melt_quote(&quote_id)
@@ -283,8 +292,8 @@ pub async fn get_check_melt_bolt11_quote(
 /// Requests tokens to be destroyed and sent out via Lightning.
 pub async fn post_melt_bolt11(
     State(state): State<MintState>,
-    Json(payload): Json<MeltBolt11Request>,
-) -> Result<Json<MeltQuoteBolt11Response>, Response> {
+    Json(payload): Json<MeltBolt11Request<Uuid>>,
+) -> Result<Json<MeltQuoteBolt11Response<Uuid>>, Response> {
     let res = state
         .mint
         .melt_bolt11(&payload)

+ 0 - 1
crates/cdk-fake-wallet/Cargo.toml

@@ -20,7 +20,6 @@ tracing = { version = "0.1", default-features = false, features = ["attributes",
 thiserror = "1"
 serde = "1"
 serde_json = "1"
-uuid = { version = "1", features = ["v4"] }
 lightning-invoice = { version = "0.32.0", features = ["serde", "std"] }
 tokio-stream = "0.1.15"
 rand = "0.8.5"

+ 2 - 2
crates/cdk-integration-tests/tests/regtest.rs

@@ -120,7 +120,7 @@ async fn test_regtest_mint_melt_round_trip() -> Result<()> {
         _ => panic!("Wrong payload"),
     };
     assert_eq!(payload.amount + payload.fee_reserve, 100.into());
-    assert_eq!(payload.quote, melt.id);
+    assert_eq!(payload.quote.to_string(), melt.id);
     assert_eq!(payload.state, MeltQuoteState::Unpaid);
 
     // get current state
@@ -131,7 +131,7 @@ async fn test_regtest_mint_melt_round_trip() -> Result<()> {
         _ => panic!("Wrong payload"),
     };
     assert_eq!(payload.amount + payload.fee_reserve, 100.into());
-    assert_eq!(payload.quote, melt.id);
+    assert_eq!(payload.quote.to_string(), melt.id);
     assert_eq!(payload.state, MeltQuoteState::Paid);
 
     Ok(())

+ 1 - 0
crates/cdk-redb/Cargo.toml

@@ -24,3 +24,4 @@ tracing = { version = "0.1", default-features = false, features = ["attributes",
 serde = { version = "1", default-features = false, features = ["derive"] }
 serde_json = "1"
 lightning-invoice = { version = "0.32.0", features = ["serde", "std"] }
+uuid = { version = "1", features = ["v4", "serde"] }

+ 156 - 5
crates/cdk-redb/src/mint/migrations.rs

@@ -8,12 +8,15 @@ use cdk::mint_url::MintUrl;
 use cdk::nuts::{CurrencyUnit, MintQuoteState, Proof, State};
 use cdk::Amount;
 use lightning_invoice::Bolt11Invoice;
-use redb::{Database, MultimapTableDefinition, ReadableTable, TableDefinition};
+use redb::{
+    Database, MultimapTableDefinition, ReadableMultimapTable, ReadableTable, TableDefinition,
+};
 use serde::{Deserialize, Serialize};
+use uuid::Uuid;
 
 use super::{Error, PROOFS_STATE_TABLE, PROOFS_TABLE, QUOTE_SIGNATURES_TABLE};
 
-const MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_quotes");
+const ID_STR_MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_quotes");
 const PENDING_PROOFS_TABLE: TableDefinition<[u8; 33], &str> =
     TableDefinition::new("pending_proofs");
 const SPENT_PROOFS_TABLE: TableDefinition<[u8; 33], &str> = TableDefinition::new("spent_proofs");
@@ -36,10 +39,158 @@ pub fn migrate_03_to_04(db: Arc<Database>) -> Result<u32, Error> {
     Ok(4)
 }
 
+pub fn migrate_04_to_05(db: Arc<Database>) -> Result<u32, Error> {
+    let write_txn = db.begin_write()?;
+
+    // Mint quotes
+    {
+        const MINT_QUOTE_TABLE_NAME: &str = "mint_quotes";
+        const OLD_TABLE: TableDefinition<&str, &str> = TableDefinition::new(MINT_QUOTE_TABLE_NAME);
+        const NEW_TABLE: TableDefinition<[u8; 16], &str> =
+            TableDefinition::new(MINT_QUOTE_TABLE_NAME);
+
+        let old_table = write_txn.open_table(OLD_TABLE)?;
+
+        let mut tmp_hashmap = HashMap::new();
+
+        for (k, v) in old_table.iter().map_err(Error::from)?.flatten() {
+            let quote_id = Uuid::try_parse(k.value()).unwrap();
+            tmp_hashmap.insert(quote_id, v.value().to_string());
+        }
+
+        write_txn.delete_table(old_table).map_err(Error::from)?;
+        let mut new_table = write_txn.open_table(NEW_TABLE)?;
+
+        for (k, v) in tmp_hashmap.into_iter() {
+            new_table
+                .insert(k.as_bytes(), v.as_str())
+                .map_err(Error::from)?;
+        }
+    }
+    // Melt quotes
+    {
+        const MELT_QUOTE_TABLE_NAME: &str = "melt_quotes";
+        const OLD_TABLE: TableDefinition<&str, &str> = TableDefinition::new(MELT_QUOTE_TABLE_NAME);
+        const NEW_TABLE: TableDefinition<[u8; 16], &str> =
+            TableDefinition::new(MELT_QUOTE_TABLE_NAME);
+
+        let old_table = write_txn.open_table(OLD_TABLE)?;
+
+        let mut tmp_hashmap = HashMap::new();
+
+        for (k, v) in old_table.iter().map_err(Error::from)?.flatten() {
+            let quote_id = Uuid::try_parse(k.value()).unwrap();
+            tmp_hashmap.insert(quote_id, v.value().to_string());
+        }
+
+        write_txn.delete_table(old_table).map_err(Error::from)?;
+        let mut new_table = write_txn.open_table(NEW_TABLE)?;
+
+        for (k, v) in tmp_hashmap.into_iter() {
+            new_table
+                .insert(k.as_bytes(), v.as_str())
+                .map_err(Error::from)?;
+        }
+    }
+    // Quote proofs
+    {
+        const QUOTE_PROOFS_TABLE_NAME: &str = "quote_proofs";
+        const OLD_TABLE: MultimapTableDefinition<&str, [u8; 33]> =
+            MultimapTableDefinition::new(QUOTE_PROOFS_TABLE_NAME);
+        const NEW_TABLE: MultimapTableDefinition<[u8; 16], [u8; 33]> =
+            MultimapTableDefinition::new(QUOTE_PROOFS_TABLE_NAME);
+
+        let old_table = write_txn.open_multimap_table(OLD_TABLE)?;
+
+        let mut tmp_hashmap = HashMap::new();
+
+        for (k, v) in old_table.iter().map_err(Error::from)?.flatten() {
+            let quote_id = Uuid::try_parse(k.value()).unwrap();
+            let ys: Vec<[u8; 33]> = v.into_iter().flatten().map(|v| v.value()).collect();
+            tmp_hashmap.insert(quote_id, ys);
+        }
+
+        write_txn
+            .delete_multimap_table(old_table)
+            .map_err(Error::from)?;
+        let mut new_table = write_txn.open_multimap_table(NEW_TABLE)?;
+
+        for (quote_id, blind_messages) in tmp_hashmap.into_iter() {
+            for blind_message in blind_messages {
+                new_table
+                    .insert(quote_id.as_bytes(), blind_message)
+                    .map_err(Error::from)?;
+            }
+        }
+    }
+    // Quote signatures
+    {
+        const QUOTE_SIGNATURES_TABLE_NAME: &str = "quote_signatures";
+        const OLD_TABLE: MultimapTableDefinition<&str, [u8; 33]> =
+            MultimapTableDefinition::new(QUOTE_SIGNATURES_TABLE_NAME);
+        const NEW_TABLE: MultimapTableDefinition<[u8; 16], [u8; 33]> =
+            MultimapTableDefinition::new(QUOTE_SIGNATURES_TABLE_NAME);
+
+        let old_table = write_txn.open_multimap_table(OLD_TABLE)?;
+
+        let mut tmp_hashmap = HashMap::new();
+
+        for (k, v) in old_table.iter().map_err(Error::from)?.flatten() {
+            let quote_id = Uuid::try_parse(k.value()).unwrap();
+            let ys: Vec<[u8; 33]> = v.into_iter().flatten().map(|v| v.value()).collect();
+            tmp_hashmap.insert(quote_id, ys);
+        }
+
+        write_txn
+            .delete_multimap_table(old_table)
+            .map_err(Error::from)?;
+        let mut new_table = write_txn.open_multimap_table(NEW_TABLE)?;
+
+        for (quote_id, signatures) in tmp_hashmap.into_iter() {
+            for signature in signatures {
+                new_table
+                    .insert(quote_id.as_bytes(), signature)
+                    .map_err(Error::from)?;
+            }
+        }
+    }
+    // Melt requests
+    {
+        const MELT_REQUESTS_TABLE_NAME: &str = "melt_requests";
+        const OLD_TABLE: TableDefinition<&str, (&str, &str)> =
+            TableDefinition::new(MELT_REQUESTS_TABLE_NAME);
+        const NEW_TABLE: TableDefinition<[u8; 16], (&str, &str)> =
+            TableDefinition::new(MELT_REQUESTS_TABLE_NAME);
+
+        let old_table = write_txn.open_table(OLD_TABLE)?;
+
+        let mut tmp_hashmap = HashMap::new();
+
+        for (k, v) in old_table.iter().map_err(Error::from)?.flatten() {
+            let quote_id = Uuid::try_parse(k.value()).unwrap();
+            let value = v.value();
+            tmp_hashmap.insert(quote_id, (value.0.to_string(), value.1.to_string()));
+        }
+
+        write_txn.delete_table(old_table).map_err(Error::from)?;
+        let mut new_table = write_txn.open_table(NEW_TABLE)?;
+
+        for (k, v) in tmp_hashmap.into_iter() {
+            new_table
+                .insert(k.as_bytes(), (v.0.as_str(), v.1.as_str()))
+                .map_err(Error::from)?;
+        }
+    }
+
+    write_txn.commit().map_err(Error::from)?;
+
+    Ok(5)
+}
+
 /// Mint Quote Info
 #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
 struct V1MintQuote {
-    pub id: String,
+    pub id: Uuid,
     pub mint_url: MintUrl,
     pub amount: Amount,
     pub unit: CurrencyUnit,
@@ -66,7 +217,7 @@ impl From<V1MintQuote> for MintQuote {
 fn migrate_mint_quotes_01_to_02(db: Arc<Database>) -> Result<(), Error> {
     let read_txn = db.begin_read().map_err(Error::from)?;
     let table = read_txn
-        .open_table(MINT_QUOTES_TABLE)
+        .open_table(ID_STR_MINT_QUOTES_TABLE)
         .map_err(Error::from)?;
 
     let mint_quotes: HashMap<String, Option<V1MintQuote>>;
@@ -94,7 +245,7 @@ fn migrate_mint_quotes_01_to_02(db: Arc<Database>) -> Result<(), Error> {
 
         {
             let mut table = write_txn
-                .open_table(MINT_QUOTES_TABLE)
+                .open_table(ID_STR_MINT_QUOTES_TABLE)
                 .map_err(Error::from)?;
             for (quote_id, quote) in migrated_mint_quotes {
                 match quote {

+ 42 - 36
crates/cdk-redb/src/mint/mod.rs

@@ -17,8 +17,9 @@ use cdk::nuts::{
 };
 use cdk::types::LnKey;
 use cdk::{cdk_database, mint};
-use migrations::migrate_01_to_02;
+use migrations::{migrate_01_to_02, migrate_04_to_05};
 use redb::{Database, MultimapTableDefinition, ReadableTable, TableDefinition};
+use uuid::Uuid;
 
 use super::error::Error;
 use crate::migrations::migrate_00_to_01;
@@ -28,22 +29,23 @@ mod migrations;
 
 const ACTIVE_KEYSETS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("active_keysets");
 const KEYSETS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("keysets");
-const MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_quotes");
-const MELT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("melt_quotes");
+const MINT_QUOTES_TABLE: TableDefinition<[u8; 16], &str> = TableDefinition::new("mint_quotes");
+const MELT_QUOTES_TABLE: TableDefinition<[u8; 16], &str> = TableDefinition::new("melt_quotes");
 const PROOFS_TABLE: TableDefinition<[u8; 33], &str> = TableDefinition::new("proofs");
 const PROOFS_STATE_TABLE: TableDefinition<[u8; 33], &str> = TableDefinition::new("proofs_state");
 const CONFIG_TABLE: TableDefinition<&str, &str> = TableDefinition::new("config");
 // Key is hex blinded_message B_ value is blinded_signature
 const BLINDED_SIGNATURES: TableDefinition<[u8; 33], &str> =
     TableDefinition::new("blinded_signatures");
-const QUOTE_PROOFS_TABLE: MultimapTableDefinition<&str, [u8; 33]> =
+const QUOTE_PROOFS_TABLE: MultimapTableDefinition<[u8; 16], [u8; 33]> =
     MultimapTableDefinition::new("quote_proofs");
-const QUOTE_SIGNATURES_TABLE: MultimapTableDefinition<&str, [u8; 33]> =
+const QUOTE_SIGNATURES_TABLE: MultimapTableDefinition<[u8; 16], [u8; 33]> =
     MultimapTableDefinition::new("quote_signatures");
 
-const MELT_REQUESTS: TableDefinition<&str, (&str, &str)> = TableDefinition::new("melt_requests");
+const MELT_REQUESTS: TableDefinition<[u8; 16], (&str, &str)> =
+    TableDefinition::new("melt_requests");
 
-const DATABASE_VERSION: u32 = 4;
+const DATABASE_VERSION: u32 = 5;
 
 /// Mint Redbdatabase
 #[derive(Debug, Clone)]
@@ -93,6 +95,10 @@ impl MintRedbDatabase {
                                 current_file_version = migrate_03_to_04(Arc::clone(&db))?;
                             }
 
+                            if current_file_version == 4 {
+                                current_file_version = migrate_04_to_05(Arc::clone(&db))?;
+                            }
+
                             if current_file_version != DATABASE_VERSION {
                                 tracing::warn!(
                                     "Database upgrade did not complete at {} current is {}",
@@ -261,7 +267,7 @@ impl MintDatabase for MintRedbDatabase {
                 .map_err(Error::from)?;
             table
                 .insert(
-                    quote.id.as_str(),
+                    quote.id.as_bytes(),
                     serde_json::to_string(&quote).map_err(Error::from)?.as_str(),
                 )
                 .map_err(Error::from)?;
@@ -271,13 +277,13 @@ impl MintDatabase for MintRedbDatabase {
         Ok(())
     }
 
-    async fn get_mint_quote(&self, quote_id: &str) -> Result<Option<MintQuote>, Self::Err> {
+    async fn get_mint_quote(&self, quote_id: &Uuid) -> Result<Option<MintQuote>, Self::Err> {
         let read_txn = self.db.begin_read().map_err(Error::from)?;
         let table = read_txn
             .open_table(MINT_QUOTES_TABLE)
             .map_err(Error::from)?;
 
-        match table.get(quote_id).map_err(Error::from)? {
+        match table.get(quote_id.as_bytes()).map_err(Error::from)? {
             Some(quote) => Ok(serde_json::from_str(quote.value()).map_err(Error::from)?),
             None => Ok(None),
         }
@@ -285,7 +291,7 @@ impl MintDatabase for MintRedbDatabase {
 
     async fn update_mint_quote_state(
         &self,
-        quote_id: &str,
+        quote_id: &Uuid,
         state: MintQuoteState,
     ) -> Result<MintQuoteState, Self::Err> {
         let write_txn = self.db.begin_write().map_err(Error::from)?;
@@ -297,7 +303,7 @@ impl MintDatabase for MintRedbDatabase {
                 .map_err(Error::from)?;
 
             let quote_guard = table
-                .get(quote_id)
+                .get(quote_id.as_bytes())
                 .map_err(Error::from)?
                 .ok_or(Error::UnknownMintInfo)?;
 
@@ -316,7 +322,7 @@ impl MintDatabase for MintRedbDatabase {
 
             table
                 .insert(
-                    quote_id,
+                    quote_id.as_bytes(),
                     serde_json::to_string(&mint_quote)
                         .map_err(Error::from)?
                         .as_str(),
@@ -376,14 +382,14 @@ impl MintDatabase for MintRedbDatabase {
         Ok(quotes)
     }
 
-    async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
+    async fn remove_mint_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err> {
         let write_txn = self.db.begin_write().map_err(Error::from)?;
 
         {
             let mut table = write_txn
                 .open_table(MINT_QUOTES_TABLE)
                 .map_err(Error::from)?;
-            table.remove(quote_id).map_err(Error::from)?;
+            table.remove(quote_id.as_bytes()).map_err(Error::from)?;
         }
         write_txn.commit().map_err(Error::from)?;
 
@@ -399,7 +405,7 @@ impl MintDatabase for MintRedbDatabase {
                 .map_err(Error::from)?;
             table
                 .insert(
-                    quote.id.as_str(),
+                    quote.id.as_bytes(),
                     serde_json::to_string(&quote).map_err(Error::from)?.as_str(),
                 )
                 .map_err(Error::from)?;
@@ -409,20 +415,20 @@ impl MintDatabase for MintRedbDatabase {
         Ok(())
     }
 
-    async fn get_melt_quote(&self, quote_id: &str) -> Result<Option<mint::MeltQuote>, Self::Err> {
+    async fn get_melt_quote(&self, quote_id: &Uuid) -> Result<Option<mint::MeltQuote>, Self::Err> {
         let read_txn = self.db.begin_read().map_err(Error::from)?;
         let table = read_txn
             .open_table(MELT_QUOTES_TABLE)
             .map_err(Error::from)?;
 
-        let quote = table.get(quote_id).map_err(Error::from)?;
+        let quote = table.get(quote_id.as_bytes()).map_err(Error::from)?;
 
         Ok(quote.map(|q| serde_json::from_str(q.value()).unwrap()))
     }
 
     async fn update_melt_quote_state(
         &self,
-        quote_id: &str,
+        quote_id: &Uuid,
         state: MeltQuoteState,
     ) -> Result<MeltQuoteState, Self::Err> {
         let write_txn = self.db.begin_write().map_err(Error::from)?;
@@ -435,7 +441,7 @@ impl MintDatabase for MintRedbDatabase {
                 .map_err(Error::from)?;
 
             let quote_guard = table
-                .get(quote_id)
+                .get(quote_id.as_bytes())
                 .map_err(Error::from)?
                 .ok_or(Error::UnknownMintInfo)?;
 
@@ -454,7 +460,7 @@ impl MintDatabase for MintRedbDatabase {
 
             table
                 .insert(
-                    quote_id,
+                    quote_id.as_bytes(),
                     serde_json::to_string(&melt_quote)
                         .map_err(Error::from)?
                         .as_str(),
@@ -483,21 +489,21 @@ impl MintDatabase for MintRedbDatabase {
         Ok(quotes)
     }
 
-    async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
+    async fn remove_melt_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err> {
         let write_txn = self.db.begin_write().map_err(Error::from)?;
 
         {
             let mut table = write_txn
                 .open_table(MELT_QUOTES_TABLE)
                 .map_err(Error::from)?;
-            table.remove(quote_id).map_err(Error::from)?;
+            table.remove(quote_id.as_bytes()).map_err(Error::from)?;
         }
         write_txn.commit().map_err(Error::from)?;
 
         Ok(())
     }
 
-    async fn add_proofs(&self, proofs: Proofs, quote_id: Option<String>) -> Result<(), Self::Err> {
+    async fn add_proofs(&self, proofs: Proofs, quote_id: Option<Uuid>) -> Result<(), Self::Err> {
         let write_txn = self.db.begin_write().map_err(Error::from)?;
 
         {
@@ -519,7 +525,7 @@ impl MintDatabase for MintRedbDatabase {
 
                 if let Some(quote_id) = &quote_id {
                     quote_proofs_table
-                        .insert(quote_id.as_str(), y)
+                        .insert(quote_id.as_bytes(), y)
                         .map_err(Error::from)?;
                 }
             }
@@ -547,13 +553,13 @@ impl MintDatabase for MintRedbDatabase {
         Ok(proofs)
     }
 
-    async fn get_proof_ys_by_quote_id(&self, quote_id: &str) -> Result<Vec<PublicKey>, Self::Err> {
+    async fn get_proof_ys_by_quote_id(&self, quote_id: &Uuid) -> Result<Vec<PublicKey>, Self::Err> {
         let read_txn = self.db.begin_read().map_err(Error::from)?;
         let table = read_txn
             .open_multimap_table(QUOTE_PROOFS_TABLE)
             .map_err(Error::from)?;
 
-        let ys = table.get(quote_id).map_err(Error::from)?;
+        let ys = table.get(quote_id.as_bytes()).map_err(Error::from)?;
 
         let proof_ys = ys.fold(Vec::new(), |mut acc, y| {
             if let Ok(y) = y {
@@ -660,7 +666,7 @@ impl MintDatabase for MintRedbDatabase {
         &self,
         blinded_messages: &[PublicKey],
         blind_signatures: &[BlindSignature],
-        quote_id: Option<String>,
+        quote_id: Option<Uuid>,
     ) -> Result<(), Self::Err> {
         let write_txn = self.db.begin_write().map_err(Error::from)?;
 
@@ -681,7 +687,7 @@ impl MintDatabase for MintRedbDatabase {
 
                 if let Some(quote_id) = &quote_id {
                     quote_sigs_table
-                        .insert(quote_id.as_str(), blinded_message.to_bytes())
+                        .insert(quote_id.as_bytes(), blinded_message.to_bytes())
                         .map_err(Error::from)?;
                 }
             }
@@ -740,7 +746,7 @@ impl MintDatabase for MintRedbDatabase {
     /// Add melt request
     async fn add_melt_request(
         &self,
-        melt_request: MeltBolt11Request,
+        melt_request: MeltBolt11Request<Uuid>,
         ln_key: LnKey,
     ) -> Result<(), Self::Err> {
         let write_txn = self.db.begin_write().map_err(Error::from)?;
@@ -748,7 +754,7 @@ impl MintDatabase for MintRedbDatabase {
 
         table
             .insert(
-                melt_request.quote.as_str(),
+                melt_request.quote.as_bytes(),
                 (
                     serde_json::to_string(&melt_request)?.as_str(),
                     serde_json::to_string(&ln_key)?.as_str(),
@@ -761,12 +767,12 @@ impl MintDatabase for MintRedbDatabase {
     /// Get melt request
     async fn get_melt_request(
         &self,
-        quote_id: &str,
-    ) -> Result<Option<(MeltBolt11Request, LnKey)>, Self::Err> {
+        quote_id: &Uuid,
+    ) -> Result<Option<(MeltBolt11Request<Uuid>, LnKey)>, Self::Err> {
         let read_txn = self.db.begin_read().map_err(Error::from)?;
         let table = read_txn.open_table(MELT_REQUESTS).map_err(Error::from)?;
 
-        match table.get(quote_id).map_err(Error::from)? {
+        match table.get(quote_id.as_bytes()).map_err(Error::from)? {
             Some(melt_request) => {
                 let (melt_request_str, ln_key_str) = melt_request.value();
                 let melt_request = serde_json::from_str(melt_request_str)?;
@@ -781,14 +787,14 @@ impl MintDatabase for MintRedbDatabase {
     /// Get [`BlindSignature`]s for quote
     async fn get_blind_signatures_for_quote(
         &self,
-        quote_id: &str,
+        quote_id: &Uuid,
     ) -> Result<Vec<BlindSignature>, Self::Err> {
         let read_txn = self.db.begin_read().map_err(Error::from)?;
         let quote_proofs_table = read_txn
             .open_multimap_table(QUOTE_SIGNATURES_TABLE)
             .map_err(Error::from)?;
 
-        let ys = quote_proofs_table.get(quote_id).unwrap();
+        let ys = quote_proofs_table.get(quote_id.as_bytes()).unwrap();
 
         let ys: Vec<[u8; 33]> = ys.into_iter().flatten().map(|v| v.value()).collect();
 

+ 2 - 1
crates/cdk-sqlite/Cargo.toml

@@ -19,7 +19,7 @@ wallet = ["cdk/wallet"]
 async-trait = "0.1"
 cdk = { path = "../cdk", version = "0.5.0", default-features = false }
 bitcoin = { version = "0.32.2", default-features = false }
-sqlx = { version = "0.6.3", default-features = false, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
+sqlx = { version = "0.6.3", default-features = false, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "uuid"] }
 thiserror = "1"
 tokio = { version = "1", features = [
     "time",
@@ -29,3 +29,4 @@ tokio = { version = "1", features = [
 tracing = { version = "0.1", default-features = false, features = ["attributes", "log"] }
 serde_json = "1"
 lightning-invoice = { version = "0.32.0", features = ["serde", "std"] }
+uuid = { version = "1", features = ["v4", "serde"] }

+ 35 - 33
crates/cdk-sqlite/src/mint/mod.rs

@@ -23,6 +23,8 @@ use error::Error;
 use lightning_invoice::Bolt11Invoice;
 use sqlx::sqlite::{SqliteConnectOptions, SqlitePool, SqlitePoolOptions, SqliteRow};
 use sqlx::Row;
+use uuid::fmt::Hyphenated;
+use uuid::Uuid;
 
 pub mod error;
 
@@ -236,7 +238,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?);
         }
     }
 
-    async fn get_mint_quote(&self, quote_id: &str) -> Result<Option<MintQuote>, Self::Err> {
+    async fn get_mint_quote(&self, quote_id: &Uuid) -> Result<Option<MintQuote>, Self::Err> {
         let mut transaction = self.pool.begin().await.map_err(Error::from)?;
         let rec = sqlx::query(
             r#"
@@ -245,7 +247,7 @@ FROM mint_quote
 WHERE id=?;
         "#,
         )
-        .bind(quote_id)
+        .bind(quote_id.as_hyphenated())
         .fetch_one(&mut transaction)
         .await;
 
@@ -345,7 +347,7 @@ WHERE request_lookup_id=?;
 
     async fn update_mint_quote_state(
         &self,
-        quote_id: &str,
+        quote_id: &Uuid,
         state: MintQuoteState,
     ) -> Result<MintQuoteState, Self::Err> {
         let mut transaction = self.pool.begin().await.map_err(Error::from)?;
@@ -357,7 +359,7 @@ FROM mint_quote
 WHERE id=?;
         "#,
         )
-        .bind(quote_id)
+        .bind(quote_id.as_hyphenated())
         .fetch_one(&mut transaction)
         .await;
         let quote = match rec {
@@ -378,7 +380,7 @@ WHERE id=?;
         "#,
         )
         .bind(state.to_string())
-        .bind(quote_id)
+        .bind(quote_id.as_hyphenated())
         .execute(&mut transaction)
         .await;
 
@@ -430,7 +432,7 @@ FROM mint_quote
         }
     }
 
-    async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
+    async fn remove_mint_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err> {
         let mut transaction = self.pool.begin().await.map_err(Error::from)?;
 
         let res = sqlx::query(
@@ -439,7 +441,7 @@ DELETE FROM mint_quote
 WHERE id=?
         "#,
         )
-        .bind(quote_id)
+        .bind(quote_id.as_hyphenated())
         .execute(&mut transaction)
         .await;
 
@@ -497,7 +499,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
             }
         }
     }
-    async fn get_melt_quote(&self, quote_id: &str) -> Result<Option<mint::MeltQuote>, Self::Err> {
+    async fn get_melt_quote(&self, quote_id: &Uuid) -> Result<Option<mint::MeltQuote>, Self::Err> {
         let mut transaction = self.pool.begin().await.map_err(Error::from)?;
         let rec = sqlx::query(
             r#"
@@ -506,7 +508,7 @@ FROM melt_quote
 WHERE id=?;
         "#,
         )
-        .bind(quote_id)
+        .bind(quote_id.as_hyphenated())
         .fetch_one(&mut transaction)
         .await;
 
@@ -564,7 +566,7 @@ FROM melt_quote
 
     async fn update_melt_quote_state(
         &self,
-        quote_id: &str,
+        quote_id: &Uuid,
         state: MeltQuoteState,
     ) -> Result<MeltQuoteState, Self::Err> {
         let mut transaction = self.pool.begin().await.map_err(Error::from)?;
@@ -576,7 +578,7 @@ FROM melt_quote
 WHERE id=?;
         "#,
         )
-        .bind(quote_id)
+        .bind(quote_id.as_hyphenated())
         .fetch_one(&mut transaction)
         .await;
 
@@ -598,7 +600,7 @@ WHERE id=?;
         "#,
         )
         .bind(state.to_string())
-        .bind(quote_id)
+        .bind(quote_id.as_hyphenated())
         .execute(&mut transaction)
         .await;
 
@@ -619,7 +621,7 @@ WHERE id=?;
         Ok(quote.state)
     }
 
-    async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
+    async fn remove_melt_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err> {
         let mut transaction = self.pool.begin().await.map_err(Error::from)?;
         let res = sqlx::query(
             r#"
@@ -627,7 +629,7 @@ DELETE FROM melt_quote
 WHERE id=?
         "#,
         )
-        .bind(quote_id)
+        .bind(quote_id.as_hyphenated())
         .execute(&mut transaction)
         .await;
 
@@ -748,7 +750,7 @@ FROM keyset;
         }
     }
 
-    async fn add_proofs(&self, proofs: Proofs, quote_id: Option<String>) -> Result<(), Self::Err> {
+    async fn add_proofs(&self, proofs: Proofs, quote_id: Option<Uuid>) -> Result<(), Self::Err> {
         let mut transaction = self.pool.begin().await.map_err(Error::from)?;
         for proof in proofs {
             if let Err(err) = sqlx::query(
@@ -765,7 +767,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?);
             .bind(proof.c.to_bytes().to_vec())
             .bind(proof.witness.map(|w| serde_json::to_string(&w).unwrap()))
             .bind("UNSPENT")
-            .bind(quote_id.clone())
+            .bind(quote_id.map(|q| q.hyphenated()))
             .execute(&mut transaction)
             .await
             .map_err(Error::from)
@@ -812,7 +814,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?);
         Ok(ys.iter().map(|y| proofs.remove(y)).collect())
     }
 
-    async fn get_proof_ys_by_quote_id(&self, quote_id: &str) -> Result<Vec<PublicKey>, Self::Err> {
+    async fn get_proof_ys_by_quote_id(&self, quote_id: &Uuid) -> Result<Vec<PublicKey>, Self::Err> {
         let mut transaction = self.pool.begin().await.map_err(Error::from)?;
 
         let rec = sqlx::query(
@@ -822,7 +824,7 @@ FROM proof
 WHERE quote_id=?;
         "#,
         )
-        .bind(quote_id)
+        .bind(quote_id.as_hyphenated())
         .fetch_all(&mut transaction)
         .await;
 
@@ -996,7 +998,7 @@ WHERE keyset_id=?;
         &self,
         blinded_messages: &[PublicKey],
         blinded_signatures: &[BlindSignature],
-        quote_id: Option<String>,
+        quote_id: Option<Uuid>,
     ) -> Result<(), Self::Err> {
         let mut transaction = self.pool.begin().await.map_err(Error::from)?;
         for (message, signature) in blinded_messages.iter().zip(blinded_signatures) {
@@ -1011,7 +1013,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?);
             .bind(u64::from(signature.amount) as i64)
             .bind(signature.keyset_id.to_string())
             .bind(signature.c.to_bytes().to_vec())
-            .bind(quote_id.clone())
+            .bind(quote_id.map(|q| q.hyphenated()))
             .bind(signature.dleq.as_ref().map(|dleq| dleq.e.to_secret_hex()))
             .bind(signature.dleq.as_ref().map(|dleq| dleq.s.to_secret_hex()))
             .execute(&mut transaction)
@@ -1111,7 +1113,7 @@ WHERE keyset_id=?;
 
     async fn add_melt_request(
         &self,
-        melt_request: MeltBolt11Request,
+        melt_request: MeltBolt11Request<Uuid>,
         ln_key: LnKey,
     ) -> Result<(), Self::Err> {
         let mut transaction = self.pool.begin().await.map_err(Error::from)?;
@@ -1149,8 +1151,8 @@ VALUES (?, ?, ?, ?, ?);
 
     async fn get_melt_request(
         &self,
-        quote_id: &str,
-    ) -> Result<Option<(MeltBolt11Request, LnKey)>, Self::Err> {
+        quote_id: &Uuid,
+    ) -> Result<Option<(MeltBolt11Request<Uuid>, LnKey)>, Self::Err> {
         let mut transaction = self.pool.begin().await.map_err(Error::from)?;
 
         let rec = sqlx::query(
@@ -1160,7 +1162,7 @@ FROM melt_request
 WHERE id=?;
         "#,
         )
-        .bind(quote_id)
+        .bind(quote_id.as_hyphenated())
         .fetch_one(&mut transaction)
         .await;
 
@@ -1192,7 +1194,7 @@ WHERE id=?;
     /// Get [`BlindSignature`]s for quote
     async fn get_blind_signatures_for_quote(
         &self,
-        quote_id: &str,
+        quote_id: &Uuid,
     ) -> Result<Vec<BlindSignature>, Self::Err> {
         let mut transaction = self.pool.begin().await.map_err(Error::from)?;
 
@@ -1203,7 +1205,7 @@ FROM blind_signature
 WHERE quote_id=?;
         "#,
         )
-        .bind(quote_id)
+        .bind(quote_id.as_hyphenated())
         .fetch_all(&mut transaction)
         .await;
 
@@ -1254,7 +1256,7 @@ fn sqlite_row_to_keyset_info(row: SqliteRow) -> Result<MintKeySetInfo, Error> {
 }
 
 fn sqlite_row_to_mint_quote(row: SqliteRow) -> Result<MintQuote, Error> {
-    let row_id: String = row.try_get("id").map_err(Error::from)?;
+    let row_id: Hyphenated = row.try_get("id").map_err(Error::from)?;
     let row_mint_url: String = row.try_get("mint_url").map_err(Error::from)?;
     let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
     let row_unit: String = row.try_get("unit").map_err(Error::from)?;
@@ -1273,7 +1275,7 @@ fn sqlite_row_to_mint_quote(row: SqliteRow) -> Result<MintQuote, Error> {
     };
 
     Ok(MintQuote {
-        id: row_id,
+        id: row_id.into_uuid(),
         mint_url: MintUrl::from_str(&row_mint_url)?,
         amount: Amount::from(row_amount as u64),
         unit: CurrencyUnit::from_str(&row_unit).map_err(Error::from)?,
@@ -1285,7 +1287,7 @@ fn sqlite_row_to_mint_quote(row: SqliteRow) -> Result<MintQuote, Error> {
 }
 
 fn sqlite_row_to_melt_quote(row: SqliteRow) -> Result<mint::MeltQuote, Error> {
-    let row_id: String = row.try_get("id").map_err(Error::from)?;
+    let row_id: Hyphenated = row.try_get("id").map_err(Error::from)?;
     let row_unit: String = row.try_get("unit").map_err(Error::from)?;
     let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
     let row_request: String = row.try_get("request").map_err(Error::from)?;
@@ -1299,7 +1301,7 @@ fn sqlite_row_to_melt_quote(row: SqliteRow) -> Result<mint::MeltQuote, Error> {
     let request_lookup_id = row_request_lookup.unwrap_or(row_request.clone());
 
     Ok(mint::MeltQuote {
-        id: row_id,
+        id: row_id.into_uuid(),
         amount: Amount::from(row_amount as u64),
         unit: CurrencyUnit::from_str(&row_unit).map_err(Error::from)?,
         request: row_request,
@@ -1375,15 +1377,15 @@ fn sqlite_row_to_blind_signature(row: SqliteRow) -> Result<BlindSignature, Error
     })
 }
 
-fn sqlite_row_to_melt_request(row: SqliteRow) -> Result<(MeltBolt11Request, LnKey), Error> {
-    let quote_id: String = row.try_get("id").map_err(Error::from)?;
+fn sqlite_row_to_melt_request(row: SqliteRow) -> Result<(MeltBolt11Request<Uuid>, LnKey), Error> {
+    let quote_id: Hyphenated = row.try_get("id").map_err(Error::from)?;
     let row_inputs: String = row.try_get("inputs").map_err(Error::from)?;
     let row_outputs: Option<String> = row.try_get("outputs").map_err(Error::from)?;
     let row_method: String = row.try_get("method").map_err(Error::from)?;
     let row_unit: String = row.try_get("unit").map_err(Error::from)?;
 
     let melt_request = MeltBolt11Request {
-        quote: quote_id,
+        quote: quote_id.into_uuid(),
         inputs: serde_json::from_str(&row_inputs)?,
         outputs: row_outputs.and_then(|o| serde_json::from_str(&o).ok()),
     };

+ 1 - 1
crates/cdk/Cargo.toml

@@ -42,7 +42,7 @@ thiserror = "1"
 futures = { version = "0.3.28", default-features = false, optional = true, features = ["alloc"] }
 url = "2.3"
 utoipa = { version = "4", optional = true }
-uuid = { version = "1", features = ["v4"] }
+uuid = { version = "1", features = ["v4", "serde"] }
 
 # -Z minimal-versions
 sync_wrapper = "0.1.2"

+ 32 - 36
crates/cdk/src/cdk_database/mint_memory.rs

@@ -5,6 +5,7 @@ use std::sync::Arc;
 
 use async_trait::async_trait;
 use tokio::sync::{Mutex, RwLock};
+use uuid::Uuid;
 
 use super::{Error, MintDatabase};
 use crate::dhke::hash_to_curve;
@@ -19,17 +20,18 @@ use crate::types::LnKey;
 
 /// Mint Memory Database
 #[derive(Debug, Clone, Default)]
+#[allow(clippy::type_complexity)]
 pub struct MintMemoryDatabase {
     active_keysets: Arc<RwLock<HashMap<CurrencyUnit, Id>>>,
     keysets: Arc<RwLock<HashMap<Id, MintKeySetInfo>>>,
-    mint_quotes: Arc<RwLock<HashMap<String, MintQuote>>>,
-    melt_quotes: Arc<RwLock<HashMap<String, mint::MeltQuote>>>,
+    mint_quotes: Arc<RwLock<HashMap<Uuid, MintQuote>>>,
+    melt_quotes: Arc<RwLock<HashMap<Uuid, mint::MeltQuote>>>,
     proofs: Arc<RwLock<HashMap<[u8; 33], Proof>>>,
     proof_state: Arc<Mutex<HashMap<[u8; 33], nut07::State>>>,
-    quote_proofs: Arc<Mutex<HashMap<String, Vec<PublicKey>>>>,
+    quote_proofs: Arc<Mutex<HashMap<Uuid, Vec<PublicKey>>>>,
     blinded_signatures: Arc<RwLock<HashMap<[u8; 33], BlindSignature>>>,
-    quote_signatures: Arc<RwLock<HashMap<String, Vec<BlindSignature>>>>,
-    melt_requests: Arc<RwLock<HashMap<String, (MeltBolt11Request, LnKey)>>>,
+    quote_signatures: Arc<RwLock<HashMap<Uuid, Vec<BlindSignature>>>>,
+    melt_requests: Arc<RwLock<HashMap<Uuid, (MeltBolt11Request<Uuid>, LnKey)>>>,
 }
 
 impl MintMemoryDatabase {
@@ -42,10 +44,10 @@ impl MintMemoryDatabase {
         melt_quotes: Vec<mint::MeltQuote>,
         pending_proofs: Proofs,
         spent_proofs: Proofs,
-        quote_proofs: HashMap<String, Vec<PublicKey>>,
+        quote_proofs: HashMap<Uuid, Vec<PublicKey>>,
         blinded_signatures: HashMap<[u8; 33], BlindSignature>,
-        quote_signatures: HashMap<String, Vec<BlindSignature>>,
-        melt_request: Vec<(MeltBolt11Request, LnKey)>,
+        quote_signatures: HashMap<Uuid, Vec<BlindSignature>>,
+        melt_request: Vec<(MeltBolt11Request<Uuid>, LnKey)>,
     ) -> Result<Self, Error> {
         let mut proofs = HashMap::new();
         let mut proof_states = HashMap::new();
@@ -64,7 +66,7 @@ impl MintMemoryDatabase {
 
         let melt_requests = melt_request
             .into_iter()
-            .map(|(request, ln_key)| (request.quote.clone(), (request, ln_key)))
+            .map(|(request, ln_key)| (request.quote, (request, ln_key)))
             .collect();
 
         Ok(Self {
@@ -73,10 +75,10 @@ impl MintMemoryDatabase {
                 keysets.into_iter().map(|k| (k.id, k)).collect(),
             )),
             mint_quotes: Arc::new(RwLock::new(
-                mint_quotes.into_iter().map(|q| (q.id.clone(), q)).collect(),
+                mint_quotes.into_iter().map(|q| (q.id, q)).collect(),
             )),
             melt_quotes: Arc::new(RwLock::new(
-                melt_quotes.into_iter().map(|q| (q.id.clone(), q)).collect(),
+                melt_quotes.into_iter().map(|q| (q.id, q)).collect(),
             )),
             proofs: Arc::new(RwLock::new(proofs)),
             proof_state: Arc::new(Mutex::new(proof_states)),
@@ -119,20 +121,17 @@ impl MintDatabase for MintMemoryDatabase {
     }
 
     async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Self::Err> {
-        self.mint_quotes
-            .write()
-            .await
-            .insert(quote.id.clone(), quote);
+        self.mint_quotes.write().await.insert(quote.id, quote);
         Ok(())
     }
 
-    async fn get_mint_quote(&self, quote_id: &str) -> Result<Option<MintQuote>, Self::Err> {
+    async fn get_mint_quote(&self, quote_id: &Uuid) -> Result<Option<MintQuote>, Self::Err> {
         Ok(self.mint_quotes.read().await.get(quote_id).cloned())
     }
 
     async fn update_mint_quote_state(
         &self,
-        quote_id: &str,
+        quote_id: &Uuid,
         state: MintQuoteState,
     ) -> Result<MintQuoteState, Self::Err> {
         let mut mint_quotes = self.mint_quotes.write().await;
@@ -146,7 +145,7 @@ impl MintDatabase for MintMemoryDatabase {
 
         quote.state = state;
 
-        mint_quotes.insert(quote_id.to_string(), quote.clone());
+        mint_quotes.insert(*quote_id, quote.clone());
 
         Ok(current_state)
     }
@@ -186,27 +185,24 @@ impl MintDatabase for MintMemoryDatabase {
         Ok(self.mint_quotes.read().await.values().cloned().collect())
     }
 
-    async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
+    async fn remove_mint_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err> {
         self.mint_quotes.write().await.remove(quote_id);
 
         Ok(())
     }
 
     async fn add_melt_quote(&self, quote: mint::MeltQuote) -> Result<(), Self::Err> {
-        self.melt_quotes
-            .write()
-            .await
-            .insert(quote.id.clone(), quote);
+        self.melt_quotes.write().await.insert(quote.id, quote);
         Ok(())
     }
 
-    async fn get_melt_quote(&self, quote_id: &str) -> Result<Option<mint::MeltQuote>, Self::Err> {
+    async fn get_melt_quote(&self, quote_id: &Uuid) -> Result<Option<mint::MeltQuote>, Self::Err> {
         Ok(self.melt_quotes.read().await.get(quote_id).cloned())
     }
 
     async fn update_melt_quote_state(
         &self,
-        quote_id: &str,
+        quote_id: &Uuid,
         state: MeltQuoteState,
     ) -> Result<MeltQuoteState, Self::Err> {
         let mut melt_quotes = self.melt_quotes.write().await;
@@ -220,7 +216,7 @@ impl MintDatabase for MintMemoryDatabase {
 
         quote.state = state;
 
-        melt_quotes.insert(quote_id.to_string(), quote.clone());
+        melt_quotes.insert(*quote_id, quote.clone());
 
         Ok(current_state)
     }
@@ -229,7 +225,7 @@ impl MintDatabase for MintMemoryDatabase {
         Ok(self.melt_quotes.read().await.values().cloned().collect())
     }
 
-    async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
+    async fn remove_melt_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err> {
         self.melt_quotes.write().await.remove(quote_id);
 
         Ok(())
@@ -237,18 +233,18 @@ impl MintDatabase for MintMemoryDatabase {
 
     async fn add_melt_request(
         &self,
-        melt_request: MeltBolt11Request,
+        melt_request: MeltBolt11Request<Uuid>,
         ln_key: LnKey,
     ) -> Result<(), Self::Err> {
         let mut melt_requests = self.melt_requests.write().await;
-        melt_requests.insert(melt_request.quote.clone(), (melt_request, ln_key));
+        melt_requests.insert(melt_request.quote, (melt_request, ln_key));
         Ok(())
     }
 
     async fn get_melt_request(
         &self,
-        quote_id: &str,
-    ) -> Result<Option<(MeltBolt11Request, LnKey)>, Self::Err> {
+        quote_id: &Uuid,
+    ) -> Result<Option<(MeltBolt11Request<Uuid>, LnKey)>, Self::Err> {
         let melt_requests = self.melt_requests.read().await;
 
         let melt_request = melt_requests.get(quote_id);
@@ -256,7 +252,7 @@ impl MintDatabase for MintMemoryDatabase {
         Ok(melt_request.cloned())
     }
 
-    async fn add_proofs(&self, proofs: Proofs, quote_id: Option<String>) -> Result<(), Self::Err> {
+    async fn add_proofs(&self, proofs: Proofs, quote_id: Option<Uuid>) -> Result<(), Self::Err> {
         let mut db_proofs = self.proofs.write().await;
 
         let mut ys = Vec::with_capacity(proofs.capacity());
@@ -293,7 +289,7 @@ impl MintDatabase for MintMemoryDatabase {
         Ok(proofs)
     }
 
-    async fn get_proof_ys_by_quote_id(&self, quote_id: &str) -> Result<Vec<PublicKey>, Self::Err> {
+    async fn get_proof_ys_by_quote_id(&self, quote_id: &Uuid) -> Result<Vec<PublicKey>, Self::Err> {
         let quote_proofs = &__self.quote_proofs.lock().await;
 
         match quote_proofs.get(quote_id) {
@@ -360,7 +356,7 @@ impl MintDatabase for MintMemoryDatabase {
         &self,
         blinded_message: &[PublicKey],
         blind_signatures: &[BlindSignature],
-        quote_id: Option<String>,
+        quote_id: Option<Uuid>,
     ) -> Result<(), Self::Err> {
         let mut current_blinded_signatures = self.blinded_signatures.write().await;
 
@@ -370,7 +366,7 @@ impl MintDatabase for MintMemoryDatabase {
 
         if let Some(quote_id) = quote_id {
             let mut current_quote_signatures = self.quote_signatures.write().await;
-            current_quote_signatures.insert(quote_id.clone(), blind_signatures.to_vec());
+            current_quote_signatures.insert(quote_id, blind_signatures.to_vec());
             let t = current_quote_signatures.get(&quote_id);
             println!("after insert: {:?}", t);
         }
@@ -411,7 +407,7 @@ impl MintDatabase for MintMemoryDatabase {
     /// Get [`BlindSignature`]s for quote
     async fn get_blind_signatures_for_quote(
         &self,
-        quote_id: &str,
+        quote_id: &Uuid,
     ) -> Result<Vec<BlindSignature>, Self::Err> {
         let ys = self.quote_signatures.read().await;
 

+ 15 - 13
crates/cdk/src/cdk_database/mod.rs

@@ -7,6 +7,8 @@ use std::fmt::Debug;
 #[cfg(any(feature = "wallet", feature = "mint"))]
 use async_trait::async_trait;
 use thiserror::Error;
+#[cfg(feature = "mint")]
+use uuid::Uuid;
 
 #[cfg(feature = "mint")]
 use crate::mint;
@@ -187,11 +189,11 @@ pub trait MintDatabase {
     /// Add [`MintMintQuote`]
     async fn add_mint_quote(&self, quote: MintMintQuote) -> Result<(), Self::Err>;
     /// Get [`MintMintQuote`]
-    async fn get_mint_quote(&self, quote_id: &str) -> Result<Option<MintMintQuote>, Self::Err>;
+    async fn get_mint_quote(&self, quote_id: &Uuid) -> Result<Option<MintMintQuote>, Self::Err>;
     /// Update state of [`MintMintQuote`]
     async fn update_mint_quote_state(
         &self,
-        quote_id: &str,
+        quote_id: &Uuid,
         state: MintQuoteState,
     ) -> Result<MintQuoteState, Self::Err>;
     /// Get all [`MintMintQuote`]s
@@ -207,34 +209,34 @@ pub trait MintDatabase {
     /// Get Mint Quotes
     async fn get_mint_quotes(&self) -> Result<Vec<MintMintQuote>, Self::Err>;
     /// Remove [`MintMintQuote`]
-    async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Self::Err>;
+    async fn remove_mint_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err>;
 
     /// Add [`mint::MeltQuote`]
     async fn add_melt_quote(&self, quote: mint::MeltQuote) -> Result<(), Self::Err>;
     /// Get [`mint::MeltQuote`]
-    async fn get_melt_quote(&self, quote_id: &str) -> Result<Option<mint::MeltQuote>, Self::Err>;
+    async fn get_melt_quote(&self, quote_id: &Uuid) -> Result<Option<mint::MeltQuote>, Self::Err>;
     /// Update [`mint::MeltQuote`] state
     async fn update_melt_quote_state(
         &self,
-        quote_id: &str,
+        quote_id: &Uuid,
         state: MeltQuoteState,
     ) -> Result<MeltQuoteState, Self::Err>;
     /// Get all [`mint::MeltQuote`]s
     async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err>;
     /// Remove [`mint::MeltQuote`]
-    async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err>;
+    async fn remove_melt_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err>;
 
     /// Add melt request
     async fn add_melt_request(
         &self,
-        melt_request: MeltBolt11Request,
+        melt_request: MeltBolt11Request<Uuid>,
         ln_key: LnKey,
     ) -> Result<(), Self::Err>;
     /// Get melt request
     async fn get_melt_request(
         &self,
-        quote_id: &str,
-    ) -> Result<Option<(MeltBolt11Request, LnKey)>, Self::Err>;
+        quote_id: &Uuid,
+    ) -> Result<Option<(MeltBolt11Request<Uuid>, LnKey)>, Self::Err>;
 
     /// Add [`MintKeySetInfo`]
     async fn add_keyset_info(&self, keyset: MintKeySetInfo) -> Result<(), Self::Err>;
@@ -244,11 +246,11 @@ pub trait MintDatabase {
     async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err>;
 
     /// Add spent [`Proofs`]
-    async fn add_proofs(&self, proof: Proofs, quote_id: Option<String>) -> Result<(), Self::Err>;
+    async fn add_proofs(&self, proof: Proofs, quote_id: Option<Uuid>) -> Result<(), Self::Err>;
     /// Get [`Proofs`] by ys
     async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err>;
     /// Get ys by quote id
-    async fn get_proof_ys_by_quote_id(&self, quote_id: &str) -> Result<Vec<PublicKey>, Self::Err>;
+    async fn get_proof_ys_by_quote_id(&self, quote_id: &Uuid) -> Result<Vec<PublicKey>, Self::Err>;
     /// Get [`Proofs`] state
     async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err>;
     /// Get [`Proofs`] state
@@ -268,7 +270,7 @@ pub trait MintDatabase {
         &self,
         blinded_messages: &[PublicKey],
         blind_signatures: &[BlindSignature],
-        quote_id: Option<String>,
+        quote_id: Option<Uuid>,
     ) -> Result<(), Self::Err>;
     /// Get [`BlindSignature`]s
     async fn get_blind_signatures(
@@ -283,6 +285,6 @@ pub trait MintDatabase {
     /// Get [`BlindSignature`]s for quote
     async fn get_blind_signatures_for_quote(
         &self,
-        quote_id: &str,
+        quote_id: &Uuid,
     ) -> Result<Vec<BlindSignature>, Self::Err>;
 }

+ 19 - 15
crates/cdk/src/mint/melt.rs

@@ -4,6 +4,7 @@ use std::str::FromStr;
 use anyhow::bail;
 use lightning_invoice::Bolt11Invoice;
 use tracing::instrument;
+use uuid::Uuid;
 
 use super::{
     CurrencyUnit, MeltBolt11Request, MeltQuote, MeltQuoteBolt11Request, MeltQuoteBolt11Response,
@@ -53,7 +54,7 @@ impl Mint {
     pub async fn get_melt_bolt11_quote(
         &self,
         melt_request: &MeltQuoteBolt11Request,
-    ) -> Result<MeltQuoteBolt11Response, Error> {
+    ) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
         let MeltQuoteBolt11Request {
             request,
             unit,
@@ -117,7 +118,10 @@ impl Mint {
 
     /// Check melt quote status
     #[instrument(skip(self))]
-    pub async fn check_melt_quote(&self, quote_id: &str) -> Result<MeltQuoteBolt11Response, Error> {
+    pub async fn check_melt_quote(
+        &self,
+        quote_id: &Uuid,
+    ) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
         let quote = self
             .localstore
             .get_melt_quote(quote_id)
@@ -159,7 +163,7 @@ impl Mint {
 
     /// Remove melt quote
     #[instrument(skip(self))]
-    pub async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Error> {
+    pub async fn remove_melt_quote(&self, quote_id: &Uuid) -> Result<(), Error> {
         self.localstore.remove_melt_quote(quote_id).await?;
 
         Ok(())
@@ -170,7 +174,7 @@ impl Mint {
     pub async fn check_melt_expected_ln_fees(
         &self,
         melt_quote: &MeltQuote,
-        melt_request: &MeltBolt11Request,
+        melt_request: &MeltBolt11Request<Uuid>,
     ) -> Result<Option<Amount>, Error> {
         let invoice = Bolt11Invoice::from_str(&melt_quote.request)?;
 
@@ -226,7 +230,7 @@ impl Mint {
     #[instrument(skip_all)]
     pub async fn verify_melt_request(
         &self,
-        melt_request: &MeltBolt11Request,
+        melt_request: &MeltBolt11Request<Uuid>,
     ) -> Result<MeltQuote, Error> {
         let state = self
             .localstore
@@ -248,10 +252,7 @@ impl Mint {
         }
 
         self.localstore
-            .add_proofs(
-                melt_request.inputs.clone(),
-                Some(melt_request.quote.clone()),
-            )
+            .add_proofs(melt_request.inputs.clone(), Some(melt_request.quote))
             .await?;
         self.check_ys_spendable(&ys, State::Pending).await?;
 
@@ -345,7 +346,10 @@ impl Mint {
     /// made The [`Proofs`] should be returned to an unspent state and the
     /// quote should be unpaid
     #[instrument(skip_all)]
-    pub async fn process_unpaid_melt(&self, melt_request: &MeltBolt11Request) -> Result<(), Error> {
+    pub async fn process_unpaid_melt(
+        &self,
+        melt_request: &MeltBolt11Request<Uuid>,
+    ) -> Result<(), Error> {
         let input_ys = melt_request.inputs.ys()?;
 
         self.localstore
@@ -373,8 +377,8 @@ impl Mint {
     #[instrument(skip_all)]
     pub async fn melt_bolt11(
         &self,
-        melt_request: &MeltBolt11Request,
-    ) -> Result<MeltQuoteBolt11Response, Error> {
+        melt_request: &MeltBolt11Request<Uuid>,
+    ) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
         use std::sync::Arc;
         async fn check_payment_state(
             ln: Arc<dyn MintLightning<Err = cdk_lightning::Error> + Send + Sync>,
@@ -584,10 +588,10 @@ impl Mint {
     #[instrument(skip_all)]
     pub async fn process_melt_request(
         &self,
-        melt_request: &MeltBolt11Request,
+        melt_request: &MeltBolt11Request<Uuid>,
         payment_preimage: Option<String>,
         total_spent: Amount,
-    ) -> Result<MeltQuoteBolt11Response, Error> {
+    ) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
         tracing::debug!("Processing melt quote: {}", melt_request.quote);
 
         let quote = self
@@ -673,7 +677,7 @@ impl Mint {
                             .map(|o| o.blinded_secret)
                             .collect::<Vec<PublicKey>>(),
                         &change_sigs,
-                        Some(quote.id.clone()),
+                        Some(quote.id),
                     )
                     .await?;
 

+ 10 - 6
crates/cdk/src/mint/mint_nut04.rs

@@ -1,4 +1,5 @@
 use tracing::instrument;
+use uuid::Uuid;
 
 use super::{
     nut04, CurrencyUnit, Mint, MintQuote, MintQuoteBolt11Request, MintQuoteBolt11Response,
@@ -59,7 +60,7 @@ impl Mint {
     pub async fn get_mint_bolt11_quote(
         &self,
         mint_quote_request: MintQuoteBolt11Request,
-    ) -> Result<MintQuoteBolt11Response, Error> {
+    ) -> Result<MintQuoteBolt11Response<Uuid>, Error> {
         let MintQuoteBolt11Request {
             amount,
             unit,
@@ -116,7 +117,7 @@ impl Mint {
 
         self.localstore.add_mint_quote(quote.clone()).await?;
 
-        let quote: MintQuoteBolt11Response = quote.into();
+        let quote: MintQuoteBolt11Response<Uuid> = quote.into();
 
         self.pubsub_manager
             .broadcast(NotificationPayload::MintQuoteBolt11Response(quote.clone()));
@@ -126,7 +127,10 @@ impl Mint {
 
     /// Check mint quote
     #[instrument(skip(self))]
-    pub async fn check_mint_quote(&self, quote_id: &str) -> Result<MintQuoteBolt11Response, Error> {
+    pub async fn check_mint_quote(
+        &self,
+        quote_id: &Uuid,
+    ) -> Result<MintQuoteBolt11Response<Uuid>, Error> {
         let quote = self
             .localstore
             .get_mint_quote(quote_id)
@@ -187,7 +191,7 @@ impl Mint {
 
     /// Remove mint quote
     #[instrument(skip_all)]
-    pub async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Error> {
+    pub async fn remove_mint_quote(&self, quote_id: &Uuid) -> Result<(), Error> {
         self.localstore.remove_mint_quote(quote_id).await?;
 
         Ok(())
@@ -250,7 +254,7 @@ impl Mint {
     #[instrument(skip_all)]
     pub async fn process_mint_request(
         &self,
-        mint_request: nut04::MintBolt11Request,
+        mint_request: nut04::MintBolt11Request<Uuid>,
     ) -> Result<nut04::MintBolt11Response, Error> {
         let mint_quote =
             if let Some(mint_quote) = self.localstore.get_mint_quote(&mint_request.quote).await? {
@@ -321,7 +325,7 @@ impl Mint {
                     .map(|p| p.blinded_secret)
                     .collect::<Vec<PublicKey>>(),
                 &blind_signatures,
-                Some(mint_request.quote.clone()),
+                Some(mint_request.quote),
             )
             .await?;
 

+ 6 - 4
crates/cdk/src/mint/mod.rs

@@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize};
 use tokio::sync::{Notify, RwLock};
 use tokio::task::JoinSet;
 use tracing::instrument;
+use uuid::Uuid;
 
 use crate::cdk_database::{self, MintDatabase};
 use crate::cdk_lightning::{self, MintLightning};
@@ -380,7 +381,7 @@ impl Mint {
     pub async fn handle_internal_melt_mint(
         &self,
         melt_quote: &MeltQuote,
-        melt_request: &MeltBolt11Request,
+        melt_request: &MeltBolt11Request<Uuid>,
     ) -> Result<Option<Amount>, Error> {
         let mint_quote = match self
             .localstore
@@ -608,6 +609,7 @@ mod tests {
 
     use bitcoin::Network;
     use secp256k1::Secp256k1;
+    use uuid::Uuid;
 
     use super::*;
     use crate::types::LnKey;
@@ -709,13 +711,13 @@ mod tests {
         pending_proofs: Proofs,
         spent_proofs: Proofs,
         blinded_signatures: HashMap<[u8; 33], BlindSignature>,
-        quote_proofs: HashMap<String, Vec<PublicKey>>,
-        quote_signatures: HashMap<String, Vec<BlindSignature>>,
+        quote_proofs: HashMap<Uuid, Vec<PublicKey>>,
+        quote_signatures: HashMap<Uuid, Vec<BlindSignature>>,
         mint_url: &'a str,
         seed: &'a [u8],
         mint_info: MintInfo,
         supported_units: HashMap<CurrencyUnit, (u64, u8)>,
-        melt_requests: Vec<(MeltBolt11Request, LnKey)>,
+        melt_requests: Vec<(MeltBolt11Request<Uuid>, LnKey)>,
         quote_ttl: QuoteTTL,
     }
 

+ 4 - 4
crates/cdk/src/mint/types.rs

@@ -12,7 +12,7 @@ use crate::Amount;
 #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
 pub struct MintQuote {
     /// Quote id
-    pub id: String,
+    pub id: Uuid,
     /// Mint Url
     pub mint_url: MintUrl,
     /// Amount of quote
@@ -43,7 +43,7 @@ impl MintQuote {
 
         Self {
             mint_url,
-            id: id.to_string(),
+            id,
             amount,
             unit,
             request,
@@ -58,7 +58,7 @@ impl MintQuote {
 #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
 pub struct MeltQuote {
     /// Quote id
-    pub id: String,
+    pub id: Uuid,
     /// Quote unit
     pub unit: CurrencyUnit,
     /// Quote amount
@@ -90,7 +90,7 @@ impl MeltQuote {
         let id = Uuid::new_v4();
 
         Self {
-            id: id.to_string(),
+            id,
             amount,
             unit,
             request,

+ 12 - 7
crates/cdk/src/nuts/nut04.rs

@@ -5,8 +5,11 @@
 use std::fmt;
 use std::str::FromStr;
 
+use serde::de::DeserializeOwned;
 use serde::{Deserialize, Serialize};
 use thiserror::Error;
+#[cfg(feature = "mint")]
+use uuid::Uuid;
 
 use super::nut00::{BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod};
 use super::MintQuoteState;
@@ -81,9 +84,10 @@ impl FromStr for QuoteState {
 /// Mint quote response [NUT-04]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
-pub struct MintQuoteBolt11Response {
+#[serde(bound = "Q: Serialize + DeserializeOwned")]
+pub struct MintQuoteBolt11Response<Q> {
     /// Quote Id
-    pub quote: String,
+    pub quote: Q,
     /// Payment request to fulfil
     pub request: String,
     /// Quote State
@@ -93,8 +97,8 @@ pub struct MintQuoteBolt11Response {
 }
 
 #[cfg(feature = "mint")]
-impl From<crate::mint::MintQuote> for MintQuoteBolt11Response {
-    fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response {
+impl From<crate::mint::MintQuote> for MintQuoteBolt11Response<Uuid> {
+    fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response<Uuid> {
         MintQuoteBolt11Response {
             quote: mint_quote.id,
             request: mint_quote.request,
@@ -107,16 +111,17 @@ impl From<crate::mint::MintQuote> for MintQuoteBolt11Response {
 /// Mint request [NUT-04]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
-pub struct MintBolt11Request {
+#[serde(bound = "Q: Serialize + DeserializeOwned")]
+pub struct MintBolt11Request<Q> {
     /// Quote id
     #[cfg_attr(feature = "swagger", schema(max_length = 1_000))]
-    pub quote: String,
+    pub quote: Q,
     /// Outputs
     #[cfg_attr(feature = "swagger", schema(max_items = 1_000))]
     pub outputs: Vec<BlindedMessage>,
 }
 
-impl MintBolt11Request {
+impl<Q> MintBolt11Request<Q> {
     /// Total [`Amount`] of outputs
     pub fn total_amount(&self) -> Result<Amount, Error> {
         Amount::try_sum(

+ 17 - 12
crates/cdk/src/nuts/nut05.rs

@@ -5,9 +5,12 @@
 use std::fmt;
 use std::str::FromStr;
 
+use serde::de::DeserializeOwned;
 use serde::{Deserialize, Deserializer, Serialize};
 use serde_json::Value;
 use thiserror::Error;
+#[cfg(feature = "mint")]
+use uuid::Uuid;
 
 use super::nut00::{BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod, Proofs};
 use super::nut15::Mpp;
@@ -88,9 +91,10 @@ impl FromStr for QuoteState {
 /// Melt quote response [NUT-05]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
 #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
-pub struct MeltQuoteBolt11Response {
+#[serde(bound = "Q: Serialize")]
+pub struct MeltQuoteBolt11Response<Q> {
     /// Quote Id
-    pub quote: String,
+    pub quote: Q,
     /// The amount that needs to be provided
     pub amount: Amount,
     /// The fee reserve that is required
@@ -112,10 +116,10 @@ pub struct MeltQuoteBolt11Response {
 }
 
 #[cfg(feature = "mint")]
-impl From<&MeltQuote> for MeltQuoteBolt11Response {
-    fn from(melt_quote: &MeltQuote) -> MeltQuoteBolt11Response {
+impl From<&MeltQuote> for MeltQuoteBolt11Response<Uuid> {
+    fn from(melt_quote: &MeltQuote) -> MeltQuoteBolt11Response<Uuid> {
         MeltQuoteBolt11Response {
-            quote: melt_quote.id.clone(),
+            quote: melt_quote.id,
             payment_preimage: None,
             change: None,
             state: melt_quote.state,
@@ -129,14 +133,14 @@ impl From<&MeltQuote> for MeltQuoteBolt11Response {
 
 // A custom deserializer is needed until all mints
 // update some will return without the required state.
-impl<'de> Deserialize<'de> for MeltQuoteBolt11Response {
+impl<'de, Q: DeserializeOwned> Deserialize<'de> for MeltQuoteBolt11Response<Q> {
     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     where
         D: Deserializer<'de>,
     {
         let value = Value::deserialize(deserializer)?;
 
-        let quote: String = serde_json::from_value(
+        let quote: Q = serde_json::from_value(
             value
                 .get("quote")
                 .ok_or(serde::de::Error::missing_field("quote"))?
@@ -212,8 +216,8 @@ impl<'de> Deserialize<'de> for MeltQuoteBolt11Response {
 }
 
 #[cfg(feature = "mint")]
-impl From<mint::MeltQuote> for MeltQuoteBolt11Response {
-    fn from(melt_quote: mint::MeltQuote) -> MeltQuoteBolt11Response {
+impl From<mint::MeltQuote> for MeltQuoteBolt11Response<Uuid> {
+    fn from(melt_quote: mint::MeltQuote) -> MeltQuoteBolt11Response<Uuid> {
         let paid = melt_quote.state == QuoteState::Paid;
         MeltQuoteBolt11Response {
             quote: melt_quote.id,
@@ -231,9 +235,10 @@ impl From<mint::MeltQuote> for MeltQuoteBolt11Response {
 /// Melt Bolt11 Request [NUT-05]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
-pub struct MeltBolt11Request {
+#[serde(bound = "Q: Serialize + DeserializeOwned")]
+pub struct MeltBolt11Request<Q> {
     /// Quote ID
-    pub quote: String,
+    pub quote: Q,
     /// Proofs
     #[cfg_attr(feature = "swagger", schema(value_type = Vec<Proof>))]
     pub inputs: Proofs,
@@ -242,7 +247,7 @@ pub struct MeltBolt11Request {
     pub outputs: Option<Vec<BlindedMessage>>,
 }
 
-impl MeltBolt11Request {
+impl<Q: Serialize + DeserializeOwned> MeltBolt11Request<Q> {
     /// Total [`Amount`] of [`Proofs`]
     pub fn proofs_amount(&self) -> Result<Amount, Error> {
         Amount::try_sum(self.inputs.iter().map(|proof| proof.amount))

+ 2 - 2
crates/cdk/src/nuts/nut08.rs

@@ -5,7 +5,7 @@
 use super::nut05::{MeltBolt11Request, MeltQuoteBolt11Response};
 use crate::Amount;
 
-impl MeltBolt11Request {
+impl<Q> MeltBolt11Request<Q> {
     /// Total output [`Amount`]
     pub fn output_amount(&self) -> Option<Amount> {
         self.outputs
@@ -14,7 +14,7 @@ impl MeltBolt11Request {
     }
 }
 
-impl MeltQuoteBolt11Response {
+impl<Q> MeltQuoteBolt11Response<Q> {
     /// Total change [`Amount`]
     pub fn change_amount(&self) -> Option<Amount> {
         self.change

+ 51 - 25
crates/cdk/src/nuts/nut17/mod.rs

@@ -1,6 +1,7 @@
 //! Specific Subscription for the cdk crate
 
 use std::ops::Deref;
+use std::str::FromStr;
 use std::sync::Arc;
 
 use serde::{Deserialize, Serialize};
@@ -8,7 +9,9 @@ use serde::{Deserialize, Serialize};
 mod on_subscription;
 
 pub use on_subscription::OnSubscription;
+use uuid::Uuid;
 
+use super::PublicKey;
 use crate::cdk_database::{self, MintDatabase};
 use crate::nuts::{
     BlindSignature, CurrencyUnit, MeltQuoteBolt11Response, MeltQuoteState, MintQuoteBolt11Response,
@@ -91,9 +94,9 @@ pub enum NotificationPayload {
     /// Proof State
     ProofState(ProofState),
     /// Melt Quote Bolt11 Response
-    MeltQuoteBolt11Response(MeltQuoteBolt11Response),
+    MeltQuoteBolt11Response(MeltQuoteBolt11Response<Uuid>),
     /// Mint Quote Bolt11 Response
-    MintQuoteBolt11Response(MintQuoteBolt11Response),
+    MintQuoteBolt11Response(MintQuoteBolt11Response<Uuid>),
 }
 
 impl From<ProofState> for NotificationPayload {
@@ -102,37 +105,42 @@ impl From<ProofState> for NotificationPayload {
     }
 }
 
-impl From<MeltQuoteBolt11Response> for NotificationPayload {
-    fn from(melt_quote: MeltQuoteBolt11Response) -> NotificationPayload {
+impl From<MeltQuoteBolt11Response<Uuid>> for NotificationPayload {
+    fn from(melt_quote: MeltQuoteBolt11Response<Uuid>) -> NotificationPayload {
         NotificationPayload::MeltQuoteBolt11Response(melt_quote)
     }
 }
 
-impl From<MintQuoteBolt11Response> for NotificationPayload {
-    fn from(mint_quote: MintQuoteBolt11Response) -> NotificationPayload {
+impl From<MintQuoteBolt11Response<Uuid>> for NotificationPayload {
+    fn from(mint_quote: MintQuoteBolt11Response<Uuid>) -> NotificationPayload {
         NotificationPayload::MintQuoteBolt11Response(mint_quote)
     }
 }
 
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+/// A parsed notification
+pub enum Notification {
+    /// ProofState id is a Pubkey
+    ProofState(PublicKey),
+    /// MeltQuote id is an Uuid
+    MeltQuoteBolt11(Uuid),
+    /// MintQuote id is an Uuid
+    MintQuoteBolt11(Uuid),
+}
+
 impl Indexable for NotificationPayload {
-    type Type = (String, Kind);
+    type Type = Notification;
 
     fn to_indexes(&self) -> Vec<Index<Self::Type>> {
         match self {
             NotificationPayload::ProofState(proof_state) => {
-                vec![Index::from((proof_state.y.to_hex(), Kind::ProofState))]
+                vec![Index::from(Notification::ProofState(proof_state.y))]
             }
             NotificationPayload::MeltQuoteBolt11Response(melt_quote) => {
-                vec![Index::from((
-                    melt_quote.quote.clone(),
-                    Kind::Bolt11MeltQuote,
-                ))]
+                vec![Index::from(Notification::MeltQuoteBolt11(melt_quote.quote))]
             }
             NotificationPayload::MintQuoteBolt11Response(mint_quote) => {
-                vec![Index::from((
-                    mint_quote.quote.clone(),
-                    Kind::Bolt11MintQuote,
-                ))]
+                vec![Index::from(Notification::MintQuoteBolt11(mint_quote.quote))]
             }
         }
     }
@@ -157,13 +165,27 @@ impl AsRef<SubId> for Params {
     }
 }
 
-impl From<Params> for Vec<Index<(String, Kind)>> {
+impl From<Params> for Vec<Index<Notification>> {
     fn from(val: Params) -> Self {
         let sub_id: SubscriptionGlobalId = Default::default();
         val.filters
-            .iter()
-            .map(|filter| Index::from(((filter.clone(), val.kind), val.id.clone(), sub_id)))
-            .collect()
+            .into_iter()
+            .map(|filter| {
+                let idx = match val.kind {
+                    Kind::Bolt11MeltQuote => {
+                        Notification::MeltQuoteBolt11(Uuid::from_str(&filter)?)
+                    }
+                    Kind::Bolt11MintQuote => {
+                        Notification::MintQuoteBolt11(Uuid::from_str(&filter)?)
+                    }
+                    Kind::ProofState => Notification::ProofState(PublicKey::from_str(&filter)?),
+                };
+
+                Ok(Index::from((idx, val.id.clone(), sub_id)))
+            })
+            .collect::<Result<_, anyhow::Error>>()
+            .unwrap()
+        // TODO don't unwrap, move to try from
     }
 }
 
@@ -172,7 +194,7 @@ impl From<Params> for Vec<Index<(String, Kind)>> {
 ///
 /// 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, (String, Kind), OnSubscription>);
+pub struct PubSubManager(pub_sub::Manager<NotificationPayload, Notification, OnSubscription>);
 
 #[allow(clippy::default_constructed_unit_structs)]
 impl Default for PubSubManager {
@@ -188,7 +210,7 @@ impl From<Arc<dyn MintDatabase<Err = cdk_database::Error> + Send + Sync>> for Pu
 }
 
 impl Deref for PubSubManager {
-    type Target = pub_sub::Manager<NotificationPayload, (String, Kind), OnSubscription>;
+    type Target = pub_sub::Manager<NotificationPayload, Notification, OnSubscription>;
 
     fn deref(&self) -> &Self::Target {
         &self.0
@@ -202,7 +224,7 @@ impl PubSubManager {
     }
 
     /// Helper function to emit a MintQuoteBolt11Response status
-    pub fn mint_quote_bolt11_status<E: Into<MintQuoteBolt11Response>>(
+    pub fn mint_quote_bolt11_status<E: Into<MintQuoteBolt11Response<Uuid>>>(
         &self,
         quote: E,
         new_state: MintQuoteState,
@@ -214,7 +236,7 @@ impl PubSubManager {
     }
 
     /// Helper function to emit a MeltQuoteBolt11Response status
-    pub fn melt_quote_status<E: Into<MeltQuoteBolt11Response>>(
+    pub fn melt_quote_status<E: Into<MeltQuoteBolt11Response<Uuid>>>(
         &self,
         quote: E,
         payment_preimage: Option<String>,
@@ -244,7 +266,11 @@ mod test {
         let manager = PubSubManager::default();
         let params = Params {
             kind: Kind::ProofState,
-            filters: vec!["x".to_string()],
+            filters: vec![PublicKey::from_hex(
+                "02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104",
+            )
+            .unwrap()
+            .to_string()],
             id: "uno".into(),
         };
 

+ 50 - 67
crates/cdk/src/nuts/nut17/on_subscription.rs

@@ -1,10 +1,11 @@
 //! On Subscription
 //!
 //! This module contains the code that is triggered when a new subscription is created.
-use std::collections::HashMap;
 use std::sync::Arc;
 
-use super::{Kind, NotificationPayload};
+use uuid::Uuid;
+
+use super::{Notification, NotificationPayload};
 use crate::cdk_database::{self, MintDatabase};
 use crate::nuts::{MeltQuoteBolt11Response, MintQuoteBolt11Response, ProofState, PublicKey};
 use crate::pub_sub::OnNewSubscription;
@@ -22,7 +23,7 @@ pub struct OnSubscription(
 #[async_trait::async_trait]
 impl OnNewSubscription for OnSubscription {
     type Event = NotificationPayload;
-    type Index = (String, Kind);
+    type Index = Notification;
 
     async fn on_new_subscription(
         &self,
@@ -35,76 +36,58 @@ impl OnNewSubscription for OnSubscription {
         };
 
         let mut to_return = vec![];
+        let mut public_keys: Vec<PublicKey> = Vec::new();
+        let mut melt_queries = Vec::new();
+        let mut mint_queries = Vec::new();
 
-        for (kind, values) in request.iter().fold(
-            HashMap::new(),
-            |mut acc: HashMap<&Kind, Vec<&String>>, (data, kind)| {
-                acc.entry(kind).or_default().push(data);
-                acc
-            },
-        ) {
-            match kind {
-                Kind::Bolt11MeltQuote => {
-                    let queries = values
-                        .iter()
-                        .map(|id| datastore.get_melt_quote(id))
-                        .collect::<Vec<_>>();
-
-                    to_return.extend(
-                        futures::future::try_join_all(queries)
-                            .await
-                            .map(|quotes| {
-                                quotes
-                                    .into_iter()
-                                    .filter_map(|quote| quote.map(|x| x.into()))
-                                    .map(|x: MeltQuoteBolt11Response| x.into())
-                                    .collect::<Vec<_>>()
-                            })
-                            .map_err(|e| e.to_string())?,
-                    );
+        for idx in request.iter() {
+            match idx {
+                Notification::ProofState(pk) => public_keys.push(*pk),
+                Notification::MeltQuoteBolt11(uuid) => {
+                    melt_queries.push(datastore.get_melt_quote(uuid))
                 }
-                Kind::Bolt11MintQuote => {
-                    let queries = values
-                        .iter()
-                        .map(|id| datastore.get_mint_quote(id))
-                        .collect::<Vec<_>>();
-
-                    to_return.extend(
-                        futures::future::try_join_all(queries)
-                            .await
-                            .map(|quotes| {
-                                quotes
-                                    .into_iter()
-                                    .filter_map(|quote| quote.map(|x| x.into()))
-                                    .map(|x: MintQuoteBolt11Response| x.into())
-                                    .collect::<Vec<_>>()
-                            })
-                            .map_err(|e| e.to_string())?,
-                    );
-                }
-                Kind::ProofState => {
-                    let public_keys = values
-                        .iter()
-                        .map(PublicKey::from_hex)
-                        .collect::<Result<Vec<PublicKey>, _>>()
-                        .map_err(|e| e.to_string())?;
-
-                    to_return.extend(
-                        datastore
-                            .get_proofs_states(&public_keys)
-                            .await
-                            .map_err(|e| e.to_string())?
-                            .into_iter()
-                            .enumerate()
-                            .filter_map(|(idx, state)| {
-                                state.map(|state| (public_keys[idx], state).into())
-                            })
-                            .map(|state: ProofState| state.into()),
-                    );
+                Notification::MintQuoteBolt11(uuid) => {
+                    mint_queries.push(datastore.get_mint_quote(uuid))
                 }
             }
         }
 
+        to_return.extend(
+            futures::future::try_join_all(melt_queries)
+                .await
+                .map(|quotes| {
+                    quotes
+                        .into_iter()
+                        .filter_map(|quote| quote.map(|x| x.into()))
+                        .map(|x: MeltQuoteBolt11Response<Uuid>| x.into())
+                        .collect::<Vec<_>>()
+                })
+                .map_err(|e| e.to_string())?,
+        );
+        to_return.extend(
+            futures::future::try_join_all(mint_queries)
+                .await
+                .map(|quotes| {
+                    quotes
+                        .into_iter()
+                        .filter_map(|quote| quote.map(|x| x.into()))
+                        .map(|x: MintQuoteBolt11Response<Uuid>| x.into())
+                        .collect::<Vec<_>>()
+                })
+                .map_err(|e| e.to_string())?,
+        );
+
+        to_return.extend(
+            datastore
+                .get_proofs_states(public_keys.as_slice())
+                .await
+                .map_err(|e| e.to_string())?
+                .into_iter()
+                .enumerate()
+                .filter_map(|(idx, state)| state.map(|state| (public_keys[idx], state).into()))
+                .map(|state: ProofState| state.into()),
+        );
+
         Ok(to_return)
     }
 }

+ 19 - 19
crates/cdk/src/wallet/client.rs

@@ -120,7 +120,7 @@ impl HttpClientMethods for HttpClient {
         &self,
         mint_url: MintUrl,
         request: MintQuoteBolt11Request,
-    ) -> Result<MintQuoteBolt11Response, Error> {
+    ) -> Result<MintQuoteBolt11Response<String>, Error> {
         let url = mint_url.join_paths(&["v1", "mint", "quote", "bolt11"])?;
 
         let res = self
@@ -132,7 +132,7 @@ impl HttpClientMethods for HttpClient {
             .text()
             .await?;
 
-        convert_http_response!(MintQuoteBolt11Response, res)
+        convert_http_response!(MintQuoteBolt11Response<String>, res)
     }
 
     /// Mint Quote status
@@ -141,12 +141,12 @@ impl HttpClientMethods for HttpClient {
         &self,
         mint_url: MintUrl,
         quote_id: &str,
-    ) -> Result<MintQuoteBolt11Response, Error> {
+    ) -> Result<MintQuoteBolt11Response<String>, Error> {
         let url = mint_url.join_paths(&["v1", "mint", "quote", "bolt11", quote_id])?;
 
         let res = self.inner.get(url).send().await?.text().await?;
 
-        convert_http_response!(MintQuoteBolt11Response, res)
+        convert_http_response!(MintQuoteBolt11Response<String>, res)
     }
 
     /// Mint Tokens [NUT-04]
@@ -154,7 +154,7 @@ impl HttpClientMethods for HttpClient {
     async fn post_mint(
         &self,
         mint_url: MintUrl,
-        request: MintBolt11Request,
+        request: MintBolt11Request<String>,
     ) -> Result<MintBolt11Response, Error> {
         let url = mint_url.join_paths(&["v1", "mint", "bolt11"])?;
 
@@ -176,7 +176,7 @@ impl HttpClientMethods for HttpClient {
         &self,
         mint_url: MintUrl,
         request: MeltQuoteBolt11Request,
-    ) -> Result<MeltQuoteBolt11Response, Error> {
+    ) -> Result<MeltQuoteBolt11Response<String>, Error> {
         let url = mint_url.join_paths(&["v1", "melt", "quote", "bolt11"])?;
 
         let res = self
@@ -188,7 +188,7 @@ impl HttpClientMethods for HttpClient {
             .text()
             .await?;
 
-        convert_http_response!(MeltQuoteBolt11Response, res)
+        convert_http_response!(MeltQuoteBolt11Response<String>, res)
     }
 
     /// Melt Quote Status
@@ -197,12 +197,12 @@ impl HttpClientMethods for HttpClient {
         &self,
         mint_url: MintUrl,
         quote_id: &str,
-    ) -> Result<MeltQuoteBolt11Response, Error> {
+    ) -> Result<MeltQuoteBolt11Response<String>, Error> {
         let url = mint_url.join_paths(&["v1", "melt", "quote", "bolt11", quote_id])?;
 
         let res = self.inner.get(url).send().await?.text().await?;
 
-        convert_http_response!(MeltQuoteBolt11Response, res)
+        convert_http_response!(MeltQuoteBolt11Response<String>, res)
     }
 
     /// Melt [NUT-05]
@@ -211,8 +211,8 @@ impl HttpClientMethods for HttpClient {
     async fn post_melt(
         &self,
         mint_url: MintUrl,
-        request: MeltBolt11Request,
-    ) -> Result<MeltQuoteBolt11Response, Error> {
+        request: MeltBolt11Request<String>,
+    ) -> Result<MeltQuoteBolt11Response<String>, Error> {
         let url = mint_url.join_paths(&["v1", "melt", "bolt11"])?;
 
         let res = self
@@ -224,7 +224,7 @@ impl HttpClientMethods for HttpClient {
             .text()
             .await?;
 
-        convert_http_response!(MeltQuoteBolt11Response, res)
+        convert_http_response!(MeltQuoteBolt11Response<String>, res)
     }
 
     /// Swap Token [NUT-03]
@@ -319,20 +319,20 @@ pub trait HttpClientMethods: Debug {
         &self,
         mint_url: MintUrl,
         request: MintQuoteBolt11Request,
-    ) -> Result<MintQuoteBolt11Response, Error>;
+    ) -> Result<MintQuoteBolt11Response<String>, Error>;
 
     /// Mint Quote status
     async fn get_mint_quote_status(
         &self,
         mint_url: MintUrl,
         quote_id: &str,
-    ) -> Result<MintQuoteBolt11Response, Error>;
+    ) -> Result<MintQuoteBolt11Response<String>, Error>;
 
     /// Mint Tokens [NUT-04]
     async fn post_mint(
         &self,
         mint_url: MintUrl,
-        request: MintBolt11Request,
+        request: MintBolt11Request<String>,
     ) -> Result<MintBolt11Response, Error>;
 
     /// Melt Quote [NUT-05]
@@ -340,22 +340,22 @@ pub trait HttpClientMethods: Debug {
         &self,
         mint_url: MintUrl,
         request: MeltQuoteBolt11Request,
-    ) -> Result<MeltQuoteBolt11Response, Error>;
+    ) -> Result<MeltQuoteBolt11Response<String>, Error>;
 
     /// Melt Quote Status
     async fn get_melt_quote_status(
         &self,
         mint_url: MintUrl,
         quote_id: &str,
-    ) -> Result<MeltQuoteBolt11Response, Error>;
+    ) -> Result<MeltQuoteBolt11Response<String>, Error>;
 
     /// Melt [NUT-05]
     /// [Nut-08] Lightning fee return if outputs defined
     async fn post_melt(
         &self,
         mint_url: MintUrl,
-        request: MeltBolt11Request,
-    ) -> Result<MeltQuoteBolt11Response, Error>;
+        request: MeltBolt11Request<String>,
+    ) -> Result<MeltQuoteBolt11Response<String>, Error>;
 
     /// Split Token [NUT-06]
     async fn post_swap(

+ 1 - 1
crates/cdk/src/wallet/melt.rs

@@ -95,7 +95,7 @@ impl Wallet {
     pub async fn melt_quote_status(
         &self,
         quote_id: &str,
-    ) -> Result<MeltQuoteBolt11Response, Error> {
+    ) -> Result<MeltQuoteBolt11Response<String>, Error> {
         let response = self
             .client
             .get_melt_quote_status(self.mint_url.clone(), quote_id)

+ 5 - 2
crates/cdk/src/wallet/mint.rs

@@ -78,7 +78,7 @@ impl Wallet {
 
         let quote = MintQuote {
             mint_url,
-            id: quote_res.quote.clone(),
+            id: quote_res.quote,
             amount,
             unit: unit.clone(),
             request: quote_res.request,
@@ -93,7 +93,10 @@ impl Wallet {
 
     /// Check mint quote status
     #[instrument(skip(self, quote_id))]
-    pub async fn mint_quote_state(&self, quote_id: &str) -> Result<MintQuoteBolt11Response, Error> {
+    pub async fn mint_quote_state(
+        &self,
+        quote_id: &str,
+    ) -> Result<MintQuoteBolt11Response<String>, Error> {
         let response = self
             .client
             .get_mint_quote_status(self.mint_url.clone(), quote_id)