Ver Fonte

feat(wallet): update mint url

feat(cli): add change mint
thesimplekid há 8 meses atrás
pai
commit
54c50c3724

+ 5 - 0
crates/cdk-cli/src/main.rs

@@ -58,6 +58,8 @@ enum Commands {
     Burn(sub_commands::burn::BurnSubCommand),
     /// Restore proofs from seed
     Restore(sub_commands::restore::RestoreSubCommand),
+    /// Update Mint Url
+    UpdateMintUrl(sub_commands::update_mint_url::UpdateMintUrlSubCommand),
 }
 
 #[tokio::main]
@@ -145,5 +147,8 @@ async fn main() -> Result<()> {
         Commands::Restore(sub_command_args) => {
             sub_commands::restore::restore(wallet, sub_command_args).await
         }
+        Commands::UpdateMintUrl(sub_command_args) => {
+            sub_commands::update_mint_url::update_mint_url(wallet, sub_command_args).await
+        }
     }
 }

+ 1 - 0
crates/cdk-cli/src/sub_commands/mod.rs

@@ -9,3 +9,4 @@ pub mod pending_mints;
 pub mod receive;
 pub mod restore;
 pub mod send;
+pub mod update_mint_url;

+ 30 - 0
crates/cdk-cli/src/sub_commands/update_mint_url.rs

@@ -0,0 +1,30 @@
+use anyhow::Result;
+use cdk::url::UncheckedUrl;
+use cdk::wallet::Wallet;
+use clap::Args;
+
+#[derive(Args)]
+pub struct UpdateMintUrlSubCommand {
+    /// Old Mint Url
+    old_mint_url: UncheckedUrl,
+    /// New Mint Url
+    new_mint_url: UncheckedUrl,
+}
+
+pub async fn update_mint_url(
+    wallet: Wallet,
+    sub_command_args: &UpdateMintUrlSubCommand,
+) -> Result<()> {
+    let UpdateMintUrlSubCommand {
+        old_mint_url,
+        new_mint_url,
+    } = sub_command_args;
+
+    wallet
+        .update_mint_url(old_mint_url.clone(), new_mint_url.clone())
+        .await?;
+
+    println!("Mint Url changed from {} to {}", old_mint_url, new_mint_url);
+
+    Ok(())
+}

+ 74 - 0
crates/cdk-redb/src/wallet.rs

@@ -11,6 +11,7 @@ use cdk::nuts::{
 };
 use cdk::types::{MeltQuote, MintQuote, ProofInfo};
 use cdk::url::UncheckedUrl;
+use cdk::util::unix_time;
 use redb::{Database, MultimapTableDefinition, ReadableTable, TableDefinition};
 use tokio::sync::Mutex;
 use tracing::instrument;
@@ -117,6 +118,23 @@ impl WalletDatabase for RedbWalletDatabase {
     }
 
     #[instrument(skip(self))]
+    async fn remove_mint(&self, mint_url: UncheckedUrl) -> Result<(), Self::Err> {
+        let db = self.db.lock().await;
+
+        let write_txn = db.begin_write().map_err(Error::from)?;
+
+        {
+            let mut table = write_txn.open_table(MINTS_TABLE).map_err(Error::from)?;
+            table
+                .remove(mint_url.to_string().as_str())
+                .map_err(Error::from)?;
+        }
+        write_txn.commit().map_err(Error::from)?;
+
+        Ok(())
+    }
+
+    #[instrument(skip(self))]
     async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err> {
         let db = self.db.lock().await;
         let read_txn = db.begin_read().map_err(Into::<Error>::into)?;
@@ -153,6 +171,62 @@ impl WalletDatabase for RedbWalletDatabase {
     }
 
     #[instrument(skip(self))]
+    async fn update_mint_url(
+        &self,
+        old_mint_url: UncheckedUrl,
+        new_mint_url: UncheckedUrl,
+    ) -> Result<(), Self::Err> {
+        // Update proofs table
+        {
+            let proofs = self
+                .get_proofs(Some(old_mint_url.clone()), None, None, None)
+                .await
+                .map_err(Error::from)?;
+
+            if let Some(proofs) = proofs {
+                // Proofs with new url
+                let updated_proofs: Vec<ProofInfo> = proofs
+                    .clone()
+                    .into_iter()
+                    .map(|mut p| {
+                        p.mint_url = new_mint_url.clone();
+                        p
+                    })
+                    .collect();
+
+                println!("{:?}", updated_proofs);
+
+                self.add_proofs(updated_proofs).await?;
+            }
+        }
+
+        // Update mint quotes
+        {
+            let quotes = self.get_mint_quotes().await?;
+
+            let unix_time = unix_time();
+
+            let quotes: Vec<MintQuote> = quotes
+                .into_iter()
+                .filter_map(|mut q| {
+                    if q.expiry < unix_time {
+                        q.mint_url = new_mint_url.clone();
+                        Some(q)
+                    } else {
+                        None
+                    }
+                })
+                .collect();
+
+            for quote in quotes {
+                self.add_mint_quote(quote).await?;
+            }
+        }
+
+        Ok(())
+    }
+
+    #[instrument(skip(self))]
     async fn add_mint_keysets(
         &self,
         mint_url: UncheckedUrl,

+ 68 - 0
crates/cdk-rexie/src/wallet.rs

@@ -9,6 +9,7 @@ use cdk::nuts::{
 };
 use cdk::types::{MeltQuote, MintQuote, ProofInfo};
 use cdk::url::UncheckedUrl;
+use cdk::util::unix_time;
 use rexie::*;
 use thiserror::Error;
 use tokio::sync::Mutex;
@@ -128,6 +129,24 @@ impl WalletDatabase for RexieWalletDatabase {
         Ok(())
     }
 
+    async fn remove_mint(&self, mint_url: UncheckedUrl) -> Result<(), Self::Err> {
+        let rexie = self.db.lock().await;
+
+        let transaction = rexie
+            .transaction(&[MINTS], TransactionMode::ReadWrite)
+            .map_err(Error::from)?;
+
+        let mints_store = transaction.store(MINTS).map_err(Error::from)?;
+
+        let mint_url = serde_wasm_bindgen::to_value(&mint_url).map_err(Error::from)?;
+
+        mints_store.delete(&mint_url).await.map_err(Error::from)?;
+
+        transaction.done().await.map_err(Error::from)?;
+
+        Ok(())
+    }
+
     async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err> {
         let rexie = self.db.lock().await;
 
@@ -173,6 +192,55 @@ impl WalletDatabase for RexieWalletDatabase {
         Ok(mints)
     }
 
+    async fn update_mint_url(
+        &self,
+        old_mint_url: UncheckedUrl,
+        new_mint_url: UncheckedUrl,
+    ) -> Result<(), Self::Err> {
+        let proofs = self
+            .get_proofs(Some(old_mint_url), None, None, None)
+            .await
+            .map_err(Error::from)?;
+
+        if let Some(proofs) = proofs {
+            let updated_proofs: Vec<ProofInfo> = proofs
+                .clone()
+                .into_iter()
+                .map(|mut p| {
+                    p.mint_url = new_mint_url.clone();
+                    p
+                })
+                .collect();
+
+            self.add_proofs(updated_proofs).await?;
+        }
+
+        // Update mint quotes
+        {
+            let quotes = self.get_mint_quotes().await?;
+
+            let unix_time = unix_time();
+
+            let quotes: Vec<MintQuote> = quotes
+                .into_iter()
+                .filter_map(|mut q| {
+                    if q.expiry < unix_time {
+                        q.mint_url = new_mint_url.clone();
+                        Some(q)
+                    } else {
+                        None
+                    }
+                })
+                .collect();
+
+            for quote in quotes {
+                self.add_mint_quote(quote).await?;
+            }
+        }
+
+        Ok(())
+    }
+
     async fn add_mint_keysets(
         &self,
         mint_url: UncheckedUrl,

+ 42 - 0
crates/cdk-sqlite/src/wallet/mod.rs

@@ -108,6 +108,22 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
 
         Ok(())
     }
+
+    async fn remove_mint(&self, mint_url: UncheckedUrl) -> Result<(), Self::Err> {
+        sqlx::query(
+            r#"
+DELETE FROM mint
+WHERE mint_url=?
+        "#,
+        )
+        .bind(mint_url.to_string())
+        .execute(&self.pool)
+        .await
+        .map_err(Error::from)?;
+
+        Ok(())
+    }
+
     async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err> {
         let rec = sqlx::query(
             r#"
@@ -155,6 +171,32 @@ FROM mint
         Ok(mints)
     }
 
+    async fn update_mint_url(
+        &self,
+        old_mint_url: UncheckedUrl,
+        new_mint_url: UncheckedUrl,
+    ) -> Result<(), Self::Err> {
+        let tables = ["mint_quote", "proof"];
+        for table in &tables {
+            let query = format!(
+                r#"
+            UPDATE {}
+            SET mint_url = ?
+            WHERE mint_url = ?;
+            "#,
+                table
+            );
+
+            sqlx::query(&query)
+                .bind(new_mint_url.to_string())
+                .bind(old_mint_url.to_string())
+                .execute(&self.pool)
+                .await
+                .map_err(Error::from)?;
+        }
+        Ok(())
+    }
+
     async fn add_mint_keysets(
         &self,
         mint_url: UncheckedUrl,

+ 6 - 0
crates/cdk/src/cdk_database/mod.rs

@@ -56,8 +56,14 @@ pub trait WalletDatabase: Debug {
         mint_url: UncheckedUrl,
         mint_info: Option<MintInfo>,
     ) -> Result<(), Self::Err>;
+    async fn remove_mint(&self, mint_url: UncheckedUrl) -> Result<(), Self::Err>;
     async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err>;
     async fn get_mints(&self) -> Result<HashMap<UncheckedUrl, Option<MintInfo>>, Self::Err>;
+    async fn update_mint_url(
+        &self,
+        old_mint_url: UncheckedUrl,
+        new_mint_url: UncheckedUrl,
+    ) -> Result<(), Self::Err>;
 
     async fn add_mint_keysets(
         &self,

+ 57 - 0
crates/cdk/src/cdk_database/wallet_memory.rs

@@ -13,6 +13,7 @@ use crate::nuts::{
 };
 use crate::types::{MeltQuote, MintQuote, ProofInfo};
 use crate::url::UncheckedUrl;
+use crate::util::unix_time;
 
 #[derive(Default, Debug, Clone)]
 pub struct WalletMemoryDatabase {
@@ -71,6 +72,13 @@ impl WalletDatabase for WalletMemoryDatabase {
         Ok(())
     }
 
+    async fn remove_mint(&self, mint_url: UncheckedUrl) -> Result<(), Self::Err> {
+        let mut mints = self.mints.write().await;
+        mints.remove(&mint_url);
+
+        Ok(())
+    }
+
     async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err> {
         Ok(self.mints.read().await.get(&mint_url).cloned().flatten())
     }
@@ -79,6 +87,55 @@ impl WalletDatabase for WalletMemoryDatabase {
         Ok(self.mints.read().await.clone())
     }
 
+    async fn update_mint_url(
+        &self,
+        old_mint_url: UncheckedUrl,
+        new_mint_url: UncheckedUrl,
+    ) -> Result<(), Self::Err> {
+        let proofs = self
+            .get_proofs(Some(old_mint_url), None, None, None)
+            .await
+            .map_err(Error::from)?;
+
+        if let Some(proofs) = proofs {
+            let updated_proofs: Vec<ProofInfo> = proofs
+                .clone()
+                .into_iter()
+                .map(|mut p| {
+                    p.mint_url = new_mint_url.clone();
+                    p
+                })
+                .collect();
+
+            self.add_proofs(updated_proofs).await?;
+        }
+
+        // Update mint quotes
+        {
+            let quotes = self.get_mint_quotes().await?;
+
+            let unix_time = unix_time();
+
+            let quotes: Vec<MintQuote> = quotes
+                .into_iter()
+                .filter_map(|mut q| {
+                    if q.expiry < unix_time {
+                        q.mint_url = new_mint_url.clone();
+                        Some(q)
+                    } else {
+                        None
+                    }
+                })
+                .collect();
+
+            for quote in quotes {
+                self.add_mint_quote(quote).await?;
+            }
+        }
+
+        Ok(())
+    }
+
     async fn add_mint_keysets(
         &self,
         mint_url: UncheckedUrl,

+ 19 - 0
crates/cdk/src/wallet/mod.rs

@@ -204,6 +204,25 @@ impl Wallet {
         Ok(mint_balances)
     }
 
+    /// Update Mint information and related entries in the event a mint changes its URL
+    #[instrument(skip(self), fields(old_mint_url = %old_mint_url, new_mint_url = %new_mint_url))]
+    pub async fn update_mint_url(
+        &self,
+        old_mint_url: UncheckedUrl,
+        new_mint_url: UncheckedUrl,
+    ) -> Result<(), Error> {
+        // Adding the new url as a new mint will get the current keysets of the mint
+        self.add_mint(new_mint_url.clone()).await?;
+
+        // Where the mint_url is in the database it must be updated
+        self.localstore
+            .update_mint_url(old_mint_url.clone(), new_mint_url)
+            .await?;
+
+        self.localstore.remove_mint(old_mint_url).await?;
+        Ok(())
+    }
+
     /// Get unspent proofs for mint
     #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn get_proofs(&self, mint_url: UncheckedUrl) -> Result<Option<Proofs>, Error> {