asmo 1 тиждень тому
батько
коміт
af5b280f59

+ 34 - 38
crates/cdk-ffi/src/database.rs

@@ -800,7 +800,7 @@ where
             .inner
             .get_proofs_by_ys(cdk_ys)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+            .map_err(FfiError::database)?;
 
         Ok(result.into_iter().map(Into::into).collect())
     }
@@ -811,16 +811,12 @@ where
             .inner
             .get_mint(cdk_mint_url)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+            .map_err(FfiError::database)?;
         Ok(result.map(Into::into))
     }
 
     async fn get_mints(&self) -> Result<HashMap<MintUrl, Option<MintInfo>>, FfiError> {
-        let result = self
-            .inner
-            .get_mints()
-            .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+        let result = self.inner.get_mints().await.map_err(FfiError::database)?;
         Ok(result
             .into_iter()
             .map(|(k, v)| (k.into(), v.map(Into::into)))
@@ -836,7 +832,7 @@ where
             .inner
             .get_mint_keysets(cdk_mint_url)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+            .map_err(FfiError::database)?;
         Ok(result.map(|keysets| keysets.into_iter().map(Into::into).collect()))
     }
 
@@ -846,7 +842,7 @@ where
             .inner
             .get_keyset_by_id(&cdk_id)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+            .map_err(FfiError::database)?;
         Ok(result.map(Into::into))
     }
 
@@ -855,7 +851,7 @@ where
             .inner
             .get_mint_quote(&quote_id)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+            .map_err(FfiError::database)?;
         Ok(result.map(|q| q.into()))
     }
 
@@ -864,7 +860,7 @@ where
             .inner
             .get_mint_quotes()
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+            .map_err(FfiError::database)?;
         Ok(result.into_iter().map(|q| q.into()).collect())
     }
 
@@ -873,7 +869,7 @@ where
             .inner
             .get_unissued_mint_quotes()
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+            .map_err(FfiError::database)?;
         Ok(result.into_iter().map(|q| q.into()).collect())
     }
 
@@ -882,7 +878,7 @@ where
             .inner
             .get_melt_quote(&quote_id)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+            .map_err(FfiError::database)?;
         Ok(result.map(|q| q.into()))
     }
 
@@ -891,7 +887,7 @@ where
             .inner
             .get_melt_quotes()
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+            .map_err(FfiError::database)?;
         Ok(result.into_iter().map(|q| q.into()).collect())
     }
 
@@ -901,7 +897,7 @@ where
             .inner
             .get_keys(&cdk_id)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+            .map_err(FfiError::database)?;
         Ok(result.map(Into::into))
     }
 
@@ -928,7 +924,7 @@ where
             .inner
             .get_proofs(cdk_mint_url, cdk_unit, cdk_state, cdk_spending_conditions)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+            .map_err(FfiError::database)?;
 
         Ok(result.into_iter().map(Into::into).collect())
     }
@@ -946,7 +942,7 @@ where
         self.inner
             .get_balance(cdk_mint_url, cdk_unit, cdk_state)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn get_transaction(
@@ -958,7 +954,7 @@ where
             .inner
             .get_transaction(cdk_id)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+            .map_err(FfiError::database)?;
         Ok(result.map(Into::into))
     }
 
@@ -976,7 +972,7 @@ where
             .inner
             .list_transactions(cdk_mint_url, cdk_direction, cdk_unit)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+            .map_err(FfiError::database)?;
 
         Ok(result.into_iter().map(Into::into).collect())
     }
@@ -990,7 +986,7 @@ where
         self.inner
             .kv_read(&primary_namespace, &secondary_namespace, &key)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn kv_list(
@@ -1001,7 +997,7 @@ where
         self.inner
             .kv_list(&primary_namespace, &secondary_namespace)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn kv_write(
@@ -1014,7 +1010,7 @@ where
         self.inner
             .kv_write(&primary_namespace, &secondary_namespace, &key, &value)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn kv_remove(
@@ -1026,7 +1022,7 @@ where
         self.inner
             .kv_remove(&primary_namespace, &secondary_namespace, &key)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     // ========== Write methods ==========
@@ -1061,7 +1057,7 @@ where
         self.inner
             .update_proofs(cdk_added, cdk_removed_ys)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn update_proofs_state(
@@ -1077,7 +1073,7 @@ where
         self.inner
             .update_proofs_state(cdk_ys, cdk_state)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn add_transaction(&self, transaction: Transaction) -> Result<(), FfiError> {
@@ -1085,7 +1081,7 @@ where
         self.inner
             .add_transaction(cdk_transaction)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn remove_transaction(&self, transaction_id: TransactionId) -> Result<(), FfiError> {
@@ -1093,7 +1089,7 @@ where
         self.inner
             .remove_transaction(cdk_id)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn update_mint_url(
@@ -1106,7 +1102,7 @@ where
         self.inner
             .update_mint_url(cdk_old, cdk_new)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn increment_keyset_counter(&self, keyset_id: Id, count: u32) -> Result<u32, FfiError> {
@@ -1114,7 +1110,7 @@ where
         self.inner
             .increment_keyset_counter(&cdk_id, count)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn add_mint(
@@ -1127,7 +1123,7 @@ where
         self.inner
             .add_mint(cdk_mint_url, cdk_mint_info)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn remove_mint(&self, mint_url: MintUrl) -> Result<(), FfiError> {
@@ -1135,7 +1131,7 @@ where
         self.inner
             .remove_mint(cdk_mint_url)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn add_mint_keysets(
@@ -1148,7 +1144,7 @@ where
         self.inner
             .add_mint_keysets(cdk_mint_url, cdk_keysets)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), FfiError> {
@@ -1156,14 +1152,14 @@ where
         self.inner
             .add_mint_quote(cdk_quote)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn remove_mint_quote(&self, quote_id: String) -> Result<(), FfiError> {
         self.inner
             .remove_mint_quote(&quote_id)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn add_melt_quote(&self, quote: MeltQuote) -> Result<(), FfiError> {
@@ -1171,14 +1167,14 @@ where
         self.inner
             .add_melt_quote(cdk_quote)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn remove_melt_quote(&self, quote_id: String) -> Result<(), FfiError> {
         self.inner
             .remove_melt_quote(&quote_id)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn add_keys(&self, keyset: KeySet) -> Result<(), FfiError> {
@@ -1186,7 +1182,7 @@ where
         self.inner
             .add_keys(cdk_keyset)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 
     async fn remove_keys(&self, id: Id) -> Result<(), FfiError> {
@@ -1194,7 +1190,7 @@ where
         self.inner
             .remove_keys(&cdk_id)
             .await
-            .map_err(|e| FfiError::Database { msg: e.to_string() })
+            .map_err(FfiError::database)
     }
 }
 

+ 44 - 93
crates/cdk-ffi/src/error.rs

@@ -1,124 +1,75 @@
 //! FFI Error types
 
 use cdk::Error as CdkError;
+use cdk_common::error::ErrorResponse;
 
 /// FFI Error type that wraps CDK errors for cross-language use
+///
+/// This simplified error type uses protocol-compliant error codes from `ErrorCode`
+/// in `cdk-common`, reducing duplication while providing structured error information
+/// to FFI consumers.
 #[derive(Debug, thiserror::Error, uniffi::Error)]
-#[uniffi(flat_error)]
 pub enum FfiError {
-    /// Generic error with message
-    #[error("CDK Error: {msg}")]
-    Generic { msg: String },
-
-    /// Amount overflow
-    #[error("Amount overflow")]
-    AmountOverflow,
-
-    /// Division by zero
-    #[error("Division by zero")]
-    DivisionByZero,
-
-    /// Amount error
-    #[error("Amount error: {msg}")]
-    Amount { msg: String },
-
-    /// Payment failed
-    #[error("Payment failed")]
-    PaymentFailed,
-
-    /// Payment pending
-    #[error("Payment pending")]
-    PaymentPending,
-
-    /// Insufficient funds
-    #[error("Insufficient funds")]
-    InsufficientFunds,
-
-    /// Database error
-    #[error("Database error: {msg}")]
-    Database { msg: String },
-
-    /// Network error
-    #[error("Network error: {msg}")]
-    Network { msg: String },
-
-    /// Invalid token
-    #[error("Invalid token: {msg}")]
-    InvalidToken { msg: String },
-
-    /// Wallet error
-    #[error("Wallet error: {msg}")]
-    Wallet { msg: String },
-
-    /// Keyset unknown
-    #[error("Keyset unknown")]
-    KeysetUnknown,
-
-    /// Unit not supported
-    #[error("Unit not supported")]
-    UnitNotSupported,
-
-    /// Runtime task join error
-    #[error("Runtime task join error: {msg}")]
-    RuntimeTaskJoin { msg: String },
-
-    /// Invalid mnemonic phrase
-    #[error("Invalid mnemonic: {msg}")]
-    InvalidMnemonic { msg: String },
-
-    /// URL parsing error
-    #[error("Invalid URL: {msg}")]
-    InvalidUrl { msg: String },
-
-    /// Hex format error
-    #[error("Invalid hex format: {msg}")]
-    InvalidHex { msg: String },
+    /// CDK error with protocol-compliant error code
+    /// The code corresponds to the Cashu protocol error codes (e.g., 11001, 20001, etc.)
+    #[error("[{code}] {message}")]
+    Cdk {
+        /// Error code from the Cashu protocol specification
+        code: u32,
+        /// Human-readable error message
+        message: String,
+    },
+
+    /// Internal/infrastructure error (no protocol error code)
+    /// Used for errors that don't map to Cashu protocol codes
+    #[error("{message}")]
+    Internal {
+        /// Human-readable error message
+        message: String,
+    },
+}
 
-    /// Cryptographic key parsing error
-    #[error("Invalid cryptographic key: {msg}")]
-    InvalidCryptographicKey { msg: String },
+impl FfiError {
+    /// Create an internal error from any type that implements ToString
+    pub fn internal(msg: impl ToString) -> Self {
+        FfiError::Internal {
+            message: msg.to_string(),
+        }
+    }
 
-    /// Serialization/deserialization error
-    #[error("Serialization error: {msg}")]
-    Serialization { msg: String },
+    /// Create a database error (uses Unknown code 50000)
+    pub fn database(msg: impl ToString) -> Self {
+        FfiError::Cdk {
+            code: 50000,
+            message: msg.to_string(),
+        }
+    }
 }
 
 impl From<CdkError> for FfiError {
     fn from(err: CdkError) -> Self {
-        match err {
-            CdkError::AmountOverflow => FfiError::AmountOverflow,
-            CdkError::PaymentFailed => FfiError::PaymentFailed,
-            CdkError::PaymentPending => FfiError::PaymentPending,
-            CdkError::InsufficientFunds => FfiError::InsufficientFunds,
-            CdkError::UnsupportedUnit => FfiError::UnitNotSupported,
-            CdkError::KeysetUnknown(_) => FfiError::KeysetUnknown,
-            _ => FfiError::Generic {
-                msg: err.to_string(),
-            },
+        let response = ErrorResponse::from(err);
+        FfiError::Cdk {
+            code: response.code.to_code() as u32,
+            message: response.detail,
         }
     }
 }
 
 impl From<cdk::amount::Error> for FfiError {
     fn from(err: cdk::amount::Error) -> Self {
-        FfiError::Amount {
-            msg: err.to_string(),
-        }
+        FfiError::internal(err)
     }
 }
 
 impl From<cdk::nuts::nut00::Error> for FfiError {
     fn from(err: cdk::nuts::nut00::Error) -> Self {
-        FfiError::Generic {
-            msg: err.to_string(),
-        }
+        FfiError::internal(err)
     }
 }
 
 impl From<serde_json::Error> for FfiError {
     fn from(err: serde_json::Error) -> Self {
-        FfiError::Serialization {
-            msg: err.to_string(),
-        }
+        FfiError::internal(err)
     }
 }

+ 11 - 14
crates/cdk-ffi/src/multi_mint_wallet.rs

@@ -36,7 +36,7 @@ impl MultiMintWallet {
     ) -> Result<Self, FfiError> {
         // Parse mnemonic and generate seed without passphrase
         let m = Mnemonic::parse(&mnemonic)
-            .map_err(|e| FfiError::InvalidMnemonic { msg: e.to_string() })?;
+            .map_err(|e| FfiError::internal(format!("Invalid mnemonic: {}", e)))?;
         let seed = m.to_seed_normalized("");
 
         // Convert the FFI database trait to a CDK database implementation
@@ -51,9 +51,7 @@ impl MultiMintWallet {
             Err(_) => {
                 // No current runtime, create a new one
                 tokio::runtime::Runtime::new()
-                    .map_err(|e| FfiError::Database {
-                        msg: format!("Failed to create runtime: {}", e),
-                    })?
+                    .map_err(|e| FfiError::internal(format!("Failed to create runtime: {}", e)))?
                     .block_on(async move {
                         CdkMultiMintWallet::new(localstore, seed, unit.into()).await
                     })
@@ -75,15 +73,15 @@ impl MultiMintWallet {
     ) -> Result<Self, FfiError> {
         // Parse mnemonic and generate seed without passphrase
         let m = Mnemonic::parse(&mnemonic)
-            .map_err(|e| FfiError::InvalidMnemonic { msg: e.to_string() })?;
+            .map_err(|e| FfiError::internal(format!("Invalid mnemonic: {}", e)))?;
         let seed = m.to_seed_normalized("");
 
         // Convert the FFI database trait to a CDK database implementation
         let localstore = crate::database::create_cdk_database_from_ffi(db);
 
         // Parse proxy URL
-        let proxy_url =
-            url::Url::parse(&proxy_url).map_err(|e| FfiError::InvalidUrl { msg: e.to_string() })?;
+        let proxy_url = url::Url::parse(&proxy_url)
+            .map_err(|e| FfiError::internal(format!("Invalid URL: {}", e)))?;
 
         let wallet = match tokio::runtime::Handle::try_current() {
             Ok(handle) => tokio::task::block_in_place(|| {
@@ -95,9 +93,7 @@ impl MultiMintWallet {
             Err(_) => {
                 // No current runtime, create a new one
                 tokio::runtime::Runtime::new()
-                    .map_err(|e| FfiError::Database {
-                        msg: format!("Failed to create runtime: {}", e),
-                    })?
+                    .map_err(|e| FfiError::internal(format!("Failed to create runtime: {}", e)))?
                     .block_on(async move {
                         CdkMultiMintWallet::new_with_proxy(localstore, seed, unit.into(), proxy_url)
                             .await
@@ -137,9 +133,10 @@ impl MultiMintWallet {
             wallet.set_metadata_cache_ttl(ttl);
             Ok(())
         } else {
-            Err(FfiError::Generic {
-                msg: format!("Mint not found: {}", cdk_mint_url),
-            })
+            Err(FfiError::internal(format!(
+                "Mint not found: {}",
+                cdk_mint_url
+            )))
         }
     }
 
@@ -809,7 +806,7 @@ impl MultiMintWallet {
             .inner
             .wait_for_nostr_payment(info_inner)
             .await
-            .map_err(|e| FfiError::Generic { msg: e.to_string() })?;
+            .map_err(FfiError::internal)?;
         Ok(amount.into())
     }
 }

+ 1 - 1
crates/cdk-ffi/src/postgres.rs

@@ -50,7 +50,7 @@ impl WalletPostgresDatabase {
             Err(_) => pg_runtime()
                 .block_on(async move { cdk_postgres::new_wallet_pg_database(url.as_str()).await }),
         }
-        .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+        .map_err(FfiError::database)?;
         Ok(Arc::new(WalletPostgresDatabase {
             inner: FfiWalletSQLDatabase::new(inner),
         }))

+ 4 - 8
crates/cdk-ffi/src/sqlite.rs

@@ -28,13 +28,11 @@ impl WalletSqliteDatabase {
             Err(_) => {
                 // No current runtime, create a new one
                 tokio::runtime::Runtime::new()
-                    .map_err(|e| FfiError::Database {
-                        msg: format!("Failed to create runtime: {}", e),
-                    })?
+                    .map_err(|e| FfiError::internal(format!("Failed to create runtime: {}", e)))?
                     .block_on(async move { CdkWalletSqliteDatabase::new(file_path.as_str()).await })
             }
         }
-        .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+        .map_err(FfiError::database)?;
         Ok(Arc::new(Self {
             inner: FfiWalletSQLDatabase::new(db),
         }))
@@ -50,13 +48,11 @@ impl WalletSqliteDatabase {
             Err(_) => {
                 // No current runtime, create a new one
                 tokio::runtime::Runtime::new()
-                    .map_err(|e| FfiError::Database {
-                        msg: format!("Failed to create runtime: {}", e),
-                    })?
+                    .map_err(|e| FfiError::internal(format!("Failed to create runtime: {}", e)))?
                     .block_on(async move { cdk_sqlite::wallet::memory::empty().await })
             }
         }
-        .map_err(|e| FfiError::Database { msg: e.to_string() })?;
+        .map_err(FfiError::database)?;
         Ok(Arc::new(Self {
             inner: FfiWalletSQLDatabase::new(db),
         }))

+ 2 - 2
crates/cdk-ffi/src/token.rs

@@ -23,7 +23,7 @@ impl FromStr for Token {
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
         let token = cdk::nuts::Token::from_str(s)
-            .map_err(|e| FfiError::InvalidToken { msg: e.to_string() })?;
+            .map_err(|e| FfiError::internal(format!("Invalid token: {}", e)))?;
         Ok(Token { inner: token })
     }
 }
@@ -46,7 +46,7 @@ impl Token {
     #[uniffi::constructor]
     pub fn from_string(encoded_token: String) -> Result<Token, FfiError> {
         let token = cdk::nuts::Token::from_str(&encoded_token)
-            .map_err(|e| FfiError::InvalidToken { msg: e.to_string() })?;
+            .map_err(|e| FfiError::internal(format!("Invalid token: {}", e)))?;
         Ok(Token { inner: token })
     }
 

+ 5 - 5
crates/cdk-ffi/src/types/amount.rs

@@ -42,7 +42,7 @@ impl Amount {
         self_amount
             .checked_add(other_amount)
             .map(Into::into)
-            .ok_or(FfiError::AmountOverflow)
+            .ok_or_else(|| FfiError::internal("Amount overflow"))
     }
 
     pub fn subtract(&self, other: Amount) -> Result<Amount, FfiError> {
@@ -51,7 +51,7 @@ impl Amount {
         self_amount
             .checked_sub(other_amount)
             .map(Into::into)
-            .ok_or(FfiError::AmountOverflow)
+            .ok_or_else(|| FfiError::internal("Amount overflow"))
     }
 
     pub fn multiply(&self, factor: u64) -> Result<Amount, FfiError> {
@@ -60,19 +60,19 @@ impl Amount {
         self_amount
             .checked_mul(factor_amount)
             .map(Into::into)
-            .ok_or(FfiError::AmountOverflow)
+            .ok_or_else(|| FfiError::internal("Amount overflow"))
     }
 
     pub fn divide(&self, divisor: u64) -> Result<Amount, FfiError> {
         if divisor == 0 {
-            return Err(FfiError::DivisionByZero);
+            return Err(FfiError::internal("Division by zero"));
         }
         let self_amount = CdkAmount::from(self.value);
         let divisor_amount = CdkAmount::from(divisor);
         self_amount
             .checked_div(divisor_amount)
             .map(Into::into)
-            .ok_or(FfiError::AmountOverflow)
+            .ok_or_else(|| FfiError::internal("Amount overflow"))
     }
 }
 

+ 4 - 6
crates/cdk-ffi/src/types/keys.rs

@@ -82,9 +82,7 @@ impl TryFrom<PublicKey> for cdk::nuts::PublicKey {
     fn try_from(key: PublicKey) -> Result<Self, Self::Error> {
         key.hex
             .parse()
-            .map_err(|e| FfiError::InvalidCryptographicKey {
-                msg: format!("Invalid public key: {}", e),
-            })
+            .map_err(|e| FfiError::internal(format!("Invalid public key: {}", e)))
     }
 }
 
@@ -127,7 +125,7 @@ impl TryFrom<Keys> for cdk::nuts::Keys {
         for (amount_u64, pubkey_hex) in keys.keys {
             let amount = cdk::Amount::from(amount_u64);
             let pubkey = cdk::nuts::PublicKey::from_str(&pubkey_hex)
-                .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?;
+                .map_err(|e| FfiError::internal(format!("Invalid public key: {}", e)))?;
             keys_map.insert(amount, pubkey);
         }
 
@@ -198,7 +196,7 @@ impl TryFrom<KeySet> for cdk::nuts::KeySet {
 
         // Convert id
         let id = cdk::nuts::Id::from_str(&keyset.id)
-            .map_err(|e| FfiError::Serialization { msg: e.to_string() })?;
+            .map_err(|e| FfiError::internal(format!("Invalid keyset ID: {}", e)))?;
 
         // Convert unit
         let unit: cdk::nuts::CurrencyUnit = keyset.unit.into();
@@ -208,7 +206,7 @@ impl TryFrom<KeySet> for cdk::nuts::KeySet {
         for (amount_u64, pubkey_hex) in keyset.keys {
             let amount = cdk::Amount::from(amount_u64);
             let pubkey = cdk::nuts::PublicKey::from_str(&pubkey_hex)
-                .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?;
+                .map_err(|e| FfiError::internal(format!("Invalid public key: {}", e)))?;
             keys_map.insert(amount, pubkey);
         }
         let keys = cdk::nuts::Keys::new(keys_map);

+ 10 - 11
crates/cdk-ffi/src/types/mint.rs

@@ -19,7 +19,7 @@ pub struct MintUrl {
 impl MintUrl {
     pub fn new(url: String) -> Result<Self, FfiError> {
         // Validate URL format
-        url::Url::parse(&url).map_err(|e| FfiError::InvalidUrl { msg: e.to_string() })?;
+        url::Url::parse(&url).map_err(|e| FfiError::internal(format!("Invalid URL: {}", e)))?;
 
         Ok(Self { url })
     }
@@ -38,7 +38,7 @@ impl TryFrom<MintUrl> for cdk::mint_url::MintUrl {
 
     fn try_from(mint_url: MintUrl) -> Result<Self, Self::Error> {
         cdk::mint_url::MintUrl::from_str(&mint_url.url)
-            .map_err(|e| FfiError::InvalidUrl { msg: e.to_string() })
+            .map_err(|e| FfiError::internal(format!("Invalid URL: {}", e)))
     }
 }
 
@@ -426,12 +426,10 @@ impl TryFrom<ProtectedEndpoint> for cdk::nuts::ProtectedEndpoint {
             "GET" => cdk::nuts::Method::Get,
             "POST" => cdk::nuts::Method::Post,
             _ => {
-                return Err(FfiError::Generic {
-                    msg: format!(
-                        "Invalid HTTP method: {}. Only GET and POST are supported",
-                        endpoint.method
-                    ),
-                })
+                return Err(FfiError::internal(format!(
+                    "Invalid HTTP method: {}. Only GET and POST are supported",
+                    endpoint.method
+                )))
             }
         };
 
@@ -467,9 +465,10 @@ impl TryFrom<ProtectedEndpoint> for cdk::nuts::ProtectedEndpoint {
                 cdk::nuts::RoutePath::Melt(NutPaymentMethod::Known(KnownMethod::Bolt12).to_string())
             }
             _ => {
-                return Err(FfiError::Generic {
-                    msg: format!("Unknown route path: {}", endpoint.path),
-                })
+                return Err(FfiError::internal(format!(
+                    "Unknown route path: {}",
+                    endpoint.path
+                )))
             }
         };
 

+ 1 - 2
crates/cdk-ffi/src/types/payment_request.rs

@@ -96,8 +96,7 @@ impl PaymentRequest {
     #[uniffi::constructor]
     pub fn from_string(encoded: String) -> Result<Arc<Self>, FfiError> {
         use std::str::FromStr;
-        let inner = cdk::nuts::PaymentRequest::from_str(&encoded)
-            .map_err(|e| FfiError::Generic { msg: e.to_string() })?;
+        let inner = cdk::nuts::PaymentRequest::from_str(&encoded).map_err(FfiError::internal)?;
         Ok(Arc::new(Self { inner }))
     }
 

+ 14 - 26
crates/cdk-ffi/src/types/proof.rs

@@ -84,11 +84,11 @@ impl TryFrom<Proof> for cdk::nuts::Proof {
         Ok(Self {
             amount: proof.amount.into(),
             secret: cdk::secret::Secret::from_str(&proof.secret)
-                .map_err(|e| FfiError::Serialization { msg: e.to_string() })?,
+                .map_err(|e| FfiError::internal(format!("Invalid secret: {}", e)))?,
             c: cdk::nuts::PublicKey::from_str(&proof.c)
-                .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?,
+                .map_err(|e| FfiError::internal(format!("Invalid public key: {}", e)))?,
             keyset_id: Id::from_str(&proof.keyset_id)
-                .map_err(|e| FfiError::Serialization { msg: e.to_string() })?,
+                .map_err(|e| FfiError::internal(format!("Invalid keyset ID: {}", e)))?,
             witness: proof.witness.map(|w| w.into()),
             dleq: proof.dleq.map(|d| d.into()),
         })
@@ -130,9 +130,7 @@ pub fn proof_has_dleq(proof: &Proof) -> bool {
 #[uniffi::export]
 pub fn proof_verify_htlc(proof: &Proof) -> Result<(), FfiError> {
     let cdk_proof: cdk::nuts::Proof = proof.clone().try_into()?;
-    cdk_proof
-        .verify_htlc()
-        .map_err(|e| FfiError::Generic { msg: e.to_string() })
+    cdk_proof.verify_htlc().map_err(FfiError::internal)
 }
 
 /// Verify DLEQ proof on a proof
@@ -145,7 +143,7 @@ pub fn proof_verify_dleq(
     let cdk_pubkey: cdk::nuts::PublicKey = mint_pubkey.try_into()?;
     cdk_proof
         .verify_dleq(cdk_pubkey)
-        .map_err(|e| FfiError::Generic { msg: e.to_string() })
+        .map_err(FfiError::internal)
 }
 
 /// Sign a P2PK proof with a secret key, returning a new signed proof
@@ -153,11 +151,11 @@ pub fn proof_verify_dleq(
 pub fn proof_sign_p2pk(proof: Proof, secret_key_hex: String) -> Result<Proof, FfiError> {
     let mut cdk_proof: cdk::nuts::Proof = proof.try_into()?;
     let secret_key = cdk::nuts::SecretKey::from_hex(&secret_key_hex)
-        .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?;
+        .map_err(|e| FfiError::internal(format!("Invalid secret key: {}", e)))?;
 
     cdk_proof
         .sign_p2pk(secret_key)
-        .map_err(|e| FfiError::Generic { msg: e.to_string() })?;
+        .map_err(FfiError::internal)?;
 
     Ok(cdk_proof.into())
 }
@@ -288,9 +286,8 @@ impl TryFrom<Conditions> for cdk::nuts::nut11::Conditions {
                     .pubkeys
                     .into_iter()
                     .map(|s| {
-                        s.parse().map_err(|e| FfiError::InvalidCryptographicKey {
-                            msg: format!("Invalid pubkey: {}", e),
-                        })
+                        s.parse()
+                            .map_err(|e| FfiError::internal(format!("Invalid pubkey: {}", e)))
                     })
                     .collect::<Result<Vec<_>, _>>()?,
             )
@@ -304,9 +301,8 @@ impl TryFrom<Conditions> for cdk::nuts::nut11::Conditions {
                     .refund_keys
                     .into_iter()
                     .map(|s| {
-                        s.parse().map_err(|e| FfiError::InvalidCryptographicKey {
-                            msg: format!("Invalid refund key: {}", e),
-                        })
+                        s.parse()
+                            .map_err(|e| FfiError::internal(format!("Invalid refund key: {}", e)))
                     })
                     .collect::<Result<Vec<_>, _>>()?,
             )
@@ -315,11 +311,7 @@ impl TryFrom<Conditions> for cdk::nuts::nut11::Conditions {
         let sig_flag = match conditions.sig_flag {
             0 => cdk::nuts::nut11::SigFlag::SigInputs,
             1 => cdk::nuts::nut11::SigFlag::SigAll,
-            _ => {
-                return Err(FfiError::Generic {
-                    msg: "Invalid sig_flag value".to_string(),
-                })
-            }
+            _ => return Err(FfiError::internal("Invalid sig_flag value")),
         };
 
         Ok(Self {
@@ -442,9 +434,7 @@ impl TryFrom<SpendingConditions> for cdk::nuts::SpendingConditions {
             SpendingConditions::P2PK { pubkey, conditions } => {
                 let pubkey = pubkey
                     .parse()
-                    .map_err(|e| FfiError::InvalidCryptographicKey {
-                        msg: format!("Invalid pubkey: {}", e),
-                    })?;
+                    .map_err(|e| FfiError::internal(format!("Invalid pubkey: {}", e)))?;
                 let conditions = conditions.map(|c| c.try_into()).transpose()?;
                 Ok(Self::P2PKConditions {
                     data: pubkey,
@@ -454,9 +444,7 @@ impl TryFrom<SpendingConditions> for cdk::nuts::SpendingConditions {
             SpendingConditions::HTLC { hash, conditions } => {
                 let hash = hash
                     .parse()
-                    .map_err(|e| FfiError::InvalidCryptographicKey {
-                        msg: format!("Invalid hash: {}", e),
-                    })?;
+                    .map_err(|e| FfiError::internal(format!("Invalid hash: {}", e)))?;
                 let conditions = conditions.map(|c| c.try_into()).transpose()?;
                 Ok(Self::HTLCConditions {
                     data: hash,

+ 1 - 1
crates/cdk-ffi/src/types/quote.rs

@@ -59,7 +59,7 @@ impl TryFrom<MintQuote> for cdk::wallet::MintQuote {
             .secret_key
             .map(|hex| cdk::nuts::SecretKey::from_hex(&hex))
             .transpose()
-            .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?;
+            .map_err(|e| FfiError::internal(format!("Invalid secret key: {}", e)))?;
 
         Ok(Self {
             id: quote.id,

+ 1 - 3
crates/cdk-ffi/src/types/subscription.rs

@@ -120,9 +120,7 @@ impl ActiveSubscription {
         guard
             .recv()
             .await
-            .ok_or(FfiError::Generic {
-                msg: "Subscription closed".to_string(),
-            })
+            .ok_or_else(|| FfiError::internal("Subscription closed"))
             .map(Into::into)
     }
 

+ 10 - 10
crates/cdk-ffi/src/types/transaction.rs

@@ -166,16 +166,16 @@ impl TransactionId {
     pub fn from_hex(hex: String) -> Result<Self, FfiError> {
         // Validate hex string length (should be 64 characters for 32 bytes)
         if hex.len() != 64 {
-            return Err(FfiError::InvalidHex {
-                msg: "Transaction ID hex must be exactly 64 characters (32 bytes)".to_string(),
-            });
+            return Err(FfiError::internal(
+                "Transaction ID hex must be exactly 64 characters (32 bytes)",
+            ));
         }
 
         // Validate hex format
         if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
-            return Err(FfiError::InvalidHex {
-                msg: "Transaction ID hex contains invalid characters".to_string(),
-            });
+            return Err(FfiError::internal(
+                "Transaction ID hex contains invalid characters",
+            ));
         }
 
         Ok(Self { hex })
@@ -206,7 +206,7 @@ impl TryFrom<TransactionId> for cdk::wallet::types::TransactionId {
 
     fn try_from(id: TransactionId) -> Result<Self, Self::Error> {
         cdk::wallet::types::TransactionId::from_hex(&id.hex)
-            .map_err(|e| FfiError::InvalidHex { msg: e.to_string() })
+            .map_err(|e| FfiError::internal(format!("Invalid transaction ID: {}", e)))
     }
 }
 
@@ -244,14 +244,14 @@ impl TryFrom<AuthProof> for cdk::nuts::AuthProof {
         use std::str::FromStr;
         Ok(Self {
             keyset_id: cdk::nuts::Id::from_str(&auth_proof.keyset_id)
-                .map_err(|e| FfiError::Serialization { msg: e.to_string() })?,
+                .map_err(|e| FfiError::internal(format!("Invalid keyset ID: {}", e)))?,
             secret: {
                 use std::str::FromStr;
                 cdk::secret::Secret::from_str(&auth_proof.secret)
-                    .map_err(|e| FfiError::Serialization { msg: e.to_string() })?
+                    .map_err(|e| FfiError::internal(format!("Invalid secret: {}", e)))?
             },
             c: cdk::nuts::PublicKey::from_str(&auth_proof.c)
-                .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?,
+                .map_err(|e| FfiError::internal(format!("Invalid public key: {}", e)))?,
             dleq: None, // FFI doesn't expose DLEQ proofs for simplicity
         })
     }

+ 14 - 18
crates/cdk-ffi/src/types/wallet.rs

@@ -192,16 +192,16 @@ impl SecretKey {
     pub fn from_hex(hex: String) -> Result<Self, FfiError> {
         // Validate hex string length (should be 64 characters for 32 bytes)
         if hex.len() != 64 {
-            return Err(FfiError::InvalidHex {
-                msg: "Secret key hex must be exactly 64 characters (32 bytes)".to_string(),
-            });
+            return Err(FfiError::internal(
+                "Secret key hex must be exactly 64 characters (32 bytes)",
+            ));
         }
 
         // Validate hex format
         if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
-            return Err(FfiError::InvalidHex {
-                msg: "Secret key hex contains invalid characters".to_string(),
-            });
+            return Err(FfiError::internal(
+                "Secret key hex contains invalid characters",
+            ));
         }
 
         Ok(Self { hex })
@@ -364,9 +364,7 @@ impl PreparedSend {
             if let Ok(mut guard) = self.inner.lock() {
                 guard.take()
             } else {
-                return Err(FfiError::Generic {
-                    msg: "Failed to acquire lock on PreparedSend".to_string(),
-                });
+                return Err(FfiError::internal("Failed to acquire lock on PreparedSend"));
             }
         };
 
@@ -375,9 +373,9 @@ impl PreparedSend {
             let token = inner.confirm(send_memo).await?;
             Ok(token.into())
         } else {
-            Err(FfiError::Generic {
-                msg: "PreparedSend has already been consumed or cancelled".to_string(),
-            })
+            Err(FfiError::internal(
+                "PreparedSend has already been consumed or cancelled",
+            ))
         }
     }
 
@@ -387,9 +385,7 @@ impl PreparedSend {
             if let Ok(mut guard) = self.inner.lock() {
                 guard.take()
             } else {
-                return Err(FfiError::Generic {
-                    msg: "Failed to acquire lock on PreparedSend".to_string(),
-                });
+                return Err(FfiError::internal("Failed to acquire lock on PreparedSend"));
             }
         };
 
@@ -397,9 +393,9 @@ impl PreparedSend {
             inner.cancel().await?;
             Ok(())
         } else {
-            Err(FfiError::Generic {
-                msg: "PreparedSend has already been consumed or cancelled".to_string(),
-            })
+            Err(FfiError::internal(
+                "PreparedSend has already been consumed or cancelled",
+            ))
         }
     }
 }

+ 18 - 23
crates/cdk-ffi/src/wallet.rs

@@ -37,23 +37,22 @@ impl Wallet {
     ) -> Result<Self, FfiError> {
         // Parse mnemonic and generate seed without passphrase
         let m = Mnemonic::parse(&mnemonic)
-            .map_err(|e| FfiError::InvalidMnemonic { msg: e.to_string() })?;
+            .map_err(|e| FfiError::internal(format!("Invalid mnemonic: {}", e)))?;
         let seed = m.to_seed_normalized("");
         tracing::info!("creating ffi wallet");
         // Convert the FFI database trait to a CDK database implementation
         let localstore = crate::database::create_cdk_database_from_ffi(db);
 
-        let wallet =
-            CdkWalletBuilder::new()
-                .mint_url(mint_url.parse().map_err(|e: cdk::mint_url::Error| {
-                    FfiError::InvalidUrl { msg: e.to_string() }
-                })?)
-                .unit(unit.into())
-                .localstore(localstore)
-                .seed(seed)
-                .target_proof_count(config.target_proof_count.unwrap_or(3) as usize)
-                .build()
-                .map_err(FfiError::from)?;
+        let wallet = CdkWalletBuilder::new()
+            .mint_url(mint_url.parse().map_err(|e: cdk::mint_url::Error| {
+                FfiError::internal(format!("Invalid URL: {}", e))
+            })?)
+            .unit(unit.into())
+            .localstore(localstore)
+            .seed(seed)
+            .target_proof_count(config.target_proof_count.unwrap_or(3) as usize)
+            .build()
+            .map_err(FfiError::from)?;
 
         Ok(Self {
             inner: Arc::new(wallet),
@@ -361,9 +360,7 @@ impl Wallet {
         let extra_value = extra
             .map(|s| serde_json::from_str(&s))
             .transpose()
-            .map_err(|e| FfiError::Generic {
-                msg: format!("Invalid extra JSON: {}", e),
-            })?;
+            .map_err(|e| FfiError::internal(format!("Invalid extra JSON: {}", e)))?;
 
         let cdk_options = options.map(Into::into);
         let quote = self
@@ -514,8 +511,7 @@ impl Wallet {
 
     /// Get fees for a specific keyset ID
     pub async fn get_keyset_fees_by_id(&self, keyset_id: String) -> Result<u64, FfiError> {
-        let id = cdk::nuts::Id::from_str(&keyset_id)
-            .map_err(|e| FfiError::Generic { msg: e.to_string() })?;
+        let id = cdk::nuts::Id::from_str(&keyset_id).map_err(FfiError::internal)?;
         Ok(self
             .inner
             .get_keyset_fees_and_amounts_by_id(id)
@@ -544,8 +540,7 @@ impl Wallet {
         proof_count: u32,
         keyset_id: String,
     ) -> Result<Amount, FfiError> {
-        let id = cdk::nuts::Id::from_str(&keyset_id)
-            .map_err(|e| FfiError::Generic { msg: e.to_string() })?;
+        let id = cdk::nuts::Id::from_str(&keyset_id).map_err(FfiError::internal)?;
         let fee = self
             .inner
             .get_keyset_count_fee(&id, proof_count as u64)
@@ -681,15 +676,15 @@ pub struct WalletConfig {
 /// Generates a new random mnemonic phrase
 #[uniffi::export]
 pub fn generate_mnemonic() -> Result<String, FfiError> {
-    let mnemonic =
-        Mnemonic::generate(12).map_err(|e| FfiError::InvalidMnemonic { msg: e.to_string() })?;
+    let mnemonic = Mnemonic::generate(12)
+        .map_err(|e| FfiError::internal(format!("Failed to generate mnemonic: {}", e)))?;
     Ok(mnemonic.to_string())
 }
 
 /// Converts a mnemonic phrase to its entropy bytes
 #[uniffi::export]
 pub fn mnemonic_to_entropy(mnemonic: String) -> Result<Vec<u8>, FfiError> {
-    let m =
-        Mnemonic::parse(&mnemonic).map_err(|e| FfiError::InvalidMnemonic { msg: e.to_string() })?;
+    let m = Mnemonic::parse(&mnemonic)
+        .map_err(|e| FfiError::internal(format!("Invalid mnemonic: {}", e)))?;
     Ok(m.to_entropy())
 }