Repository: breez/breez-sdk-greenlight (language: python)

Issue:
Nested Errors received on bindings
Here are a couple of examples we've received

- after calling `redeem_swap`: 
```
Generic(message: "Generic: Failed to create invoice: Invalid amount: Receive amount must be more than 0")
```

- after calling `send_payment`:
```
FrbAnyhowException(Generic: Generic: status: Unknown, message: "transport error", details: [], metadata: MetadataMap { headers: {} })
```

This may be because `parse_cln_error_wrapped` is failing or not applied to source of these cln errors, see it's documentation for a better idea. I'm only guessing.


Discussion / hints:
We are experiencing the same error for some payments while testing one of our testers shared this screenshot
<img width="309" alt="image" src="https://github.com/breez/breez-sdk/assets/42438775/3eceeb73-2565-4d28-bb16-af22169470c9">

We build on `react-native`

@Czino We have fixes in SDK v0.3.8 to help mitigate this Greenlight error. Can you update?
@erdemyerebasmaz it seems that the best solution here is to upgrade to flutter_rust_bridge v2. They added support structured errors.

Fix patch:
diff --git a/libs/Cargo.lock b/libs/Cargo.lock
index ea522fb00..dd90944e5 100644
--- a/libs/Cargo.lock
+++ b/libs/Cargo.lock
@@ -512,7 +512,7 @@ dependencies = [
 
 [[package]]
 name = "breez-sdk-core"
-version = "0.3.2"
+version = "0.4.0"
 dependencies = [
  "aes",
  "anyhow",
@@ -558,7 +558,7 @@ dependencies = [
 
 [[package]]
 name = "breez_sdk"
-version = "0.3.2"
+version = "0.4.0"
 dependencies = [
  "anyhow",
  "breez-sdk-core",
diff --git a/libs/Cargo.toml b/libs/Cargo.toml
index ecdc33448..3add1b61c 100644
--- a/libs/Cargo.toml
+++ b/libs/Cargo.toml
@@ -13,7 +13,7 @@ members = [
 resolver = "2"
 
 [workspace.package]
-version = "0.3.2"
+version = "0.4.0"
 
 [workspace.dependencies]
 uniffi = "0.23.0"
diff --git a/libs/sdk-core/src/breez_services.rs b/libs/sdk-core/src/breez_services.rs
index 17fb53504..d477aac6d 100644
--- a/libs/sdk-core/src/breez_services.rs
+++ b/libs/sdk-core/src/breez_services.rs
@@ -689,7 +689,7 @@ impl BreezServices {
 
     /// Convenience method to look up [LspInformation] for a given LSP ID
     pub async fn fetch_lsp_info(&self, id: String) -> SdkResult<Option<LspInformation>> {
-        Ok(get_lsp_by_id(self.persister.clone(), self.lsp_api.clone(), id.as_str()).await?)
+        get_lsp_by_id(self.persister.clone(), self.lsp_api.clone(), id.as_str()).await
     }
 
     /// Gets the fees required to open a channel for a given amount.
@@ -1277,7 +1277,7 @@ impl BreezServices {
 
     /// Convenience method to look up LSP info based on current LSP ID
     pub async fn lsp_info(&self) -> SdkResult<LspInformation> {
-        Ok(get_lsp(self.persister.clone(), self.lsp_api.clone()).await?)
+        get_lsp(self.persister.clone(), self.lsp_api.clone()).await
     }
 
     pub(crate) async fn start_node(&self) -> Result<()> {
@@ -1287,7 +1287,7 @@ impl BreezServices {
 
     /// Get the recommended fees for onchain transactions
     pub async fn recommended_fees(&self) -> SdkResult<RecommendedFees> {
-        Ok(self.chain_service.recommended_fees().await?)
+        self.chain_service.recommended_fees().await
     }
 
     /// Get the full default config for a specific environment type
@@ -1889,8 +1889,8 @@ impl BreezServices {
             .send()
             .await
             .map(|_| ())
-            .map_err(|err| SdkError::ServiceConnectivity {
-                err: format!("Failed to register for tx confirmation notifications: {err}"),
+            .map_err(|e| SdkError::ServiceConnectivity {
+                err: format!("Failed to register for tx confirmation notifications: {e}"),
             })
     }
 
@@ -2375,7 +2375,9 @@ impl Receiver for PaymentReceiver {
         let node_state = self
             .persister
             .get_node_state()?
-            .ok_or(anyhow!("Node info not found"))?;
+            .ok_or(ReceivePaymentError::Generic {
+                err: "Node info not found".into(),
+            })?;
         let expiry = req.expiry.unwrap_or(INVOICE_PAYMENT_FEE_EXPIRY_SECONDS);
 
         ensure_sdk!(
@@ -2613,22 +2615,26 @@ impl PaymentReceiver {
 async fn get_lsp(
     persister: Arc<SqliteStorage>,
     lsp_api: Arc<dyn LspAPI>,
-) -> Result<LspInformation> {
-    let lsp_id = persister.get_lsp_id()?.ok_or(anyhow!("No LSP ID found"))?;
+) -> SdkResult<LspInformation> {
+    let lsp_id = persister
+        .get_lsp_id()?
+        .ok_or(SdkError::generic("No LSP ID found"))?;
 
     get_lsp_by_id(persister, lsp_api, lsp_id.as_str())
         .await?
-        .ok_or_else(|| anyhow!("No LSP found for id {lsp_id}"))
+        .ok_or_else(|| SdkError::Generic {
+            err: format!("No LSP found for id {lsp_id}"),
+        })
 }
 
 async fn get_lsp_by_id(
     persister: Arc<SqliteStorage>,
     lsp_api: Arc<dyn LspAPI>,
     lsp_id: &str,
-) -> Result<Option<LspInformation>> {
+) -> SdkResult<Option<LspInformation>> {
     let node_pubkey = persister
         .get_node_state()?
-        .ok_or(anyhow!("Node info not found"))?
+        .ok_or(SdkError::generic("Node info not found"))?
         .id;
 
     Ok(lsp_api
diff --git a/libs/sdk-core/src/chain.rs b/libs/sdk-core/src/chain.rs
index 336e81d57..c7cbbce70 100644
--- a/libs/sdk-core/src/chain.rs
+++ b/libs/sdk-core/src/chain.rs
@@ -1,26 +1,27 @@
-use anyhow::{anyhow, Result};
+use anyhow::Result;
 use serde::{Deserialize, Serialize};
 
 use crate::bitcoin::hashes::hex::FromHex;
 use crate::bitcoin::{OutPoint, Txid};
+use crate::error::{SdkError, SdkResult};
 use crate::input_parser::{get_parse_and_log_response, get_reqwest_client, post_and_log_response};
 
 pub const DEFAULT_MEMPOOL_SPACE_URL: &str = "https://mempool.space/api";
 
 #[tonic::async_trait]
 pub trait ChainService: Send + Sync {
-    async fn recommended_fees(&self) -> Result<RecommendedFees>;
+    async fn recommended_fees(&self) -> SdkResult<RecommendedFees>;
     /// Gets up to 50 onchain and up to 25 mempool transactions associated with this address.
     ///
     /// See <https://mempool.space/docs/api/rest#get-address-transactions>
-    async fn address_transactions(&self, address: String) -> Result<Vec<OnchainTx>>;
-    async fn current_tip(&self) -> Result<u32>;
+    async fn address_transactions(&self, address: String) -> SdkResult<Vec<OnchainTx>>;
+    async fn current_tip(&self) -> SdkResult<u32>;
     /// Gets the spending status of all tx outputs for this tx.
     ///
     /// See <https://mempool.space/docs/api/rest#get-transaction-outspends>
-    async fn transaction_outspends(&self, txid: String) -> Result<Vec<Outspend>>;
+    async fn transaction_outspends(&self, txid: String) -> SdkResult<Vec<Outspend>>;
     /// If successful, it returns the transaction ID. Otherwise returns an `Err` describing the error.
-    async fn broadcast_transaction(&self, tx: Vec<u8>) -> Result<String>;
+    async fn broadcast_transaction(&self, tx: Vec<u8>) -> SdkResult<String>;
 }
 
 pub trait RedundantChainServiceTrait: ChainService {
@@ -45,7 +46,7 @@ impl RedundantChainServiceTrait for RedundantChainService {
 
 #[tonic::async_trait]
 impl ChainService for RedundantChainService {
-    async fn recommended_fees(&self) -> Result<RecommendedFees> {
+    async fn recommended_fees(&self) -> SdkResult<RecommendedFees> {
         for inst in &self.instances {
             match inst.recommended_fees().await {
                 Ok(res) => {
@@ -54,10 +55,12 @@ impl ChainService for RedundantChainService {
                 Err(e) => error!("Call to chain service {} failed: {e}", inst.base_url),
             }
         }
-        Err(anyhow!("All chain service instances failed"))
+        Err(SdkError::service_connectivity(
+            "All chain service instances failed",
+        ))
     }
 
-    async fn address_transactions(&self, address: String) -> Result<Vec<OnchainTx>> {
+    async fn address_transactions(&self, address: String) -> SdkResult<Vec<OnchainTx>> {
         for inst in &self.instances {
             match inst.address_transactions(address.clone()).await {
                 Ok(res) => {
@@ -66,10 +69,12 @@ impl ChainService for RedundantChainService {
                 Err(e) => error!("Call to chain service {} failed: {e}", inst.base_url),
             }
         }
-        Err(anyhow!("All chain service instances failed"))
+        Err(SdkError::service_connectivity(
+            "All chain service instances failed",
+        ))
     }
 
-    async fn current_tip(&self) -> Result<u32> {
+    async fn current_tip(&self) -> SdkResult<u32> {
         for inst in &self.instances {
             match inst.current_tip().await {
                 Ok(res) => {
@@ -78,10 +83,12 @@ impl ChainService for RedundantChainService {
                 Err(e) => error!("Call to chain service {} failed: {e}", inst.base_url),
             }
         }
-        Err(anyhow!("All chain service instances failed"))
+        Err(SdkError::service_connectivity(
+            "All chain service instances failed",
+        ))
     }
 
-    async fn transaction_outspends(&self, txid: String) -> Result<Vec<Outspend>> {
+    async fn transaction_outspends(&self, txid: String) -> SdkResult<Vec<Outspend>> {
         for inst in &self.instances {
             match inst.transaction_outspends(txid.clone()).await {
                 Ok(res) => {
@@ -90,10 +97,12 @@ impl ChainService for RedundantChainService {
                 Err(e) => error!("Call to chain service {} failed: {e}", inst.base_url),
             }
         }
-        Err(anyhow!("All chain service instances failed"))
+        Err(SdkError::service_connectivity(
+            "All chain service instances failed",
+        ))
     }
 
-    async fn broadcast_transaction(&self, tx: Vec<u8>) -> Result<String> {
+    async fn broadcast_transaction(&self, tx: Vec<u8>) -> SdkResult<String> {
         for inst in &self.instances {
             match inst.broadcast_transaction(tx.clone()).await {
                 Ok(res) => {
@@ -102,7 +111,9 @@ impl ChainService for RedundantChainService {
                 Err(e) => error!("Call to chain service {} failed: {e}", inst.base_url),
             }
         }
-        Err(anyhow!("All chain service instances failed"))
+        Err(SdkError::service_connectivity(
+            "All chain service instances failed",
+        ))
     }
 }
 
@@ -325,28 +336,37 @@ impl MempoolSpace {
 
 #[tonic::async_trait]
 impl ChainService for MempoolSpace {
-    async fn recommended_fees(&self) -> Result<RecommendedFees> {
+    async fn recommended_fees(&self) -> SdkResult<RecommendedFees> {
         get_parse_and_log_response(&format!("{}/v1/fees/recommended", self.base_url)).await
     }
 
-    async fn address_transactions(&self, address: String) -> Result<Vec<OnchainTx>> {
+    async fn address_transactions(&self, address: String) -> SdkResult<Vec<OnchainTx>> {
         get_parse_and_log_response(&format!("{}/address/{address}/txs", self.base_url)).await
     }
 
-    async fn current_tip(&self) -> Result<u32> {
+    async fn current_tip(&self) -> SdkResult<u32> {
         get_parse_and_log_response(&format!("{}/blocks/tip/height", self.base_url)).await
     }
 
-    async fn transaction_outspends(&self, txid: String) -> Result<Vec<Outspend>> {
+    async fn transaction_outspends(&self, txid: String) -> SdkResult<Vec<Outspend>> {
         let url = format!("{}/tx/{txid}/outspends", self.base_url);
-        Ok(get_reqwest_client()?.get(url).send().await?.json().await?)
+        Ok(get_reqwest_client()?
+            .get(url)
+            .send()
+            .await
+            .map_err(|e| SdkError::ServiceConnectivity { err: e.to_string() })?
+            .json()
+            .await
+            .map_err(|e| SdkError::ServiceConnectivity { err: e.to_string() })?)
     }
 
-    async fn broadcast_transaction(&self, tx: Vec<u8>) -> Result<String> {
+    async fn broadcast_transaction(&self, tx: Vec<u8>) -> SdkResult<String> {
         let txid_or_error =
             post_and_log_response(&format!("{}/tx", self.base_url), Some(hex::encode(tx))).await?;
         match txid_or_error.contains("error") {
-            true => Err(anyhow!("Error fetching tx: {txid_or_error}")),
+            true => Err(SdkError::Generic {
+                err: format!("Error fetching tx: {txid_or_error}"),
+            }),
             false => Ok(txid_or_error),
         }
     }
diff --git a/libs/sdk-core/src/error.rs b/libs/sdk-core/src/error.rs
index 2c662df1a..736d3ea45 100644
--- a/libs/sdk-core/src/error.rs
+++ b/libs/sdk-core/src/error.rs
@@ -39,12 +39,8 @@ impl From<bip32::Error> for ConnectError {
 impl From<NodeError> for ConnectError {
     fn from(value: NodeError) -> Self {
         match value {
-            NodeError::RestoreOnly(err) => Self::RestoreOnly {
-                err: err.to_string(),
-            },
-            NodeError::ServiceConnectivity(err) => Self::ServiceConnectivity {
-                err: err.to_string(),
-            },
+            NodeError::RestoreOnly(err) => Self::RestoreOnly { err },
+            NodeError::ServiceConnectivity(err) => Self::ServiceConnectivity { err },
             _ => Self::Generic {
                 err: value.to_string(),
             },
@@ -63,10 +59,8 @@ impl From<PersistError> for ConnectError {
 impl From<SdkError> for ConnectError {
     fn from(value: SdkError) -> Self {
         match value {
+            SdkError::Generic { err } => Self::Generic { err },
             SdkError::ServiceConnectivity { err } => Self::ServiceConnectivity { err },
-            _ => Self::Generic {
-                err: value.to_string(),
-            },
         }
     }
 }
@@ -91,12 +85,8 @@ pub enum LnUrlAuthError {
 impl From<LnUrlError> for LnUrlAuthError {
     fn from(value: LnUrlError) -> Self {
         match value {
-            LnUrlError::InvalidUri(err) => Self::InvalidUri {
-                err: err.to_string(),
-            },
-            LnUrlError::ServiceConnectivity(err) => Self::ServiceConnectivity {
-                err: err.to_string(),
-            },
+            LnUrlError::InvalidUri(err) => Self::InvalidUri { err },
+            LnUrlError::ServiceConnectivity(err) => Self::ServiceConnectivity { err },
             _ => Self::Generic {
                 err: value.to_string(),
             },
@@ -107,10 +97,8 @@ impl From<LnUrlError> for LnUrlAuthError {
 impl From<SdkError> for LnUrlAuthError {
     fn from(value: SdkError) -> Self {
         match value {
+            SdkError::Generic { err } => Self::Generic { err },
             SdkError::ServiceConnectivity { err } => Self::ServiceConnectivity { err },
-            _ => Self::Generic {
-                err: value.to_string(),
-            },
         }
     }
 }
@@ -189,12 +177,9 @@ impl From<crate::bitcoin::hashes::hex::Error> for LnUrlPayError {
 impl From<InvoiceError> for LnUrlPayError {
     fn from(value: InvoiceError) -> Self {
         match value {
-            InvoiceError::InvalidNetwork(err) => Self::InvalidNetwork {
-                err: err.to_string(),
-            },
-            _ => Self::InvalidInvoice {
-                err: value.to_string(),
-            },
+            InvoiceError::InvalidNetwork(err) => Self::InvalidNetwork { err },
+            InvoiceError::Validation(err) => Self::InvalidInvoice { err },
+            InvoiceError::Generic(err) => Self::Generic { err },
         }
     }
 }
@@ -202,13 +187,9 @@ impl From<InvoiceError> for LnUrlPayError {
 impl From<LnUrlError> for LnUrlPayError {
     fn from(value: LnUrlError) -> Self {
         match value {
-            LnUrlError::InvalidUri(err) => Self::InvalidUri {
-                err: err.to_string(),
-            },
+            LnUrlError::InvalidUri(err) => Self::InvalidUri { err },
             LnUrlError::InvalidInvoice(err) => err.into(),
-            LnUrlError::ServiceConnectivity(err) => Self::ServiceConnectivity {
-                err: err.to_string(),
-            },
+            LnUrlError::ServiceConnectivity(err) => Self::ServiceConnectivity { err },
             _ => Self::Generic {
                 err: value.to_string(),
             },
@@ -227,10 +208,8 @@ impl From<PersistError> for LnUrlPayError {
 impl From<SdkError> for LnUrlPayError {
     fn from(value: SdkError) -> Self {
         match value {
+            SdkError::Generic { err } => Self::Generic { err },
             SdkError::ServiceConnectivity { err } => Self::ServiceConnectivity { err },
-            _ => Self::Generic {
-                err: value.to_string(),
-            },
         }
     }
 }
@@ -239,6 +218,7 @@ impl From<SendPaymentError> for LnUrlPayError {
     fn from(value: SendPaymentError) -> Self {
         match value {
             SendPaymentError::AlreadyPaid => Self::AlreadyPaid,
+            SendPaymentError::Generic { err } => Self::Generic { err },
             SendPaymentError::InvalidAmount { err } => Self::InvalidAmount { err },
             SendPaymentError::InvalidInvoice { err } => Self::InvalidInvoice { err },
             SendPaymentError::InvalidNetwork { err } => Self::InvalidNetwork { err },
@@ -248,9 +228,6 @@ impl From<SendPaymentError> for LnUrlPayError {
             SendPaymentError::RouteNotFound { err } => Self::RouteNotFound { err },
             SendPaymentError::RouteTooExpensive { err } => Self::RouteTooExpensive { err },
             SendPaymentError::ServiceConnectivity { err } => Self::ServiceConnectivity { err },
-            _ => Self::Generic {
-                err: value.to_string(),
-            },
         }
     }
 }
@@ -286,18 +263,10 @@ pub enum LnUrlWithdrawError {
     ServiceConnectivity { err: String },
 }
 
-impl From<LnUrlError> for LnUrlWithdrawError {
-    fn from(value: LnUrlError) -> Self {
+impl From<InvoiceError> for LnUrlWithdrawError {
+    fn from(value: InvoiceError) -> Self {
         match value {
-            LnUrlError::InvalidUri(err) => Self::InvalidUri {
-                err: err.to_string(),
-            },
-            LnUrlError::InvalidInvoice(err) => Self::InvalidInvoice {
-                err: err.to_string(),
-            },
-            LnUrlError::ServiceConnectivity(err) => Self::ServiceConnectivity {
-                err: err.to_string(),
-            },
+            InvoiceError::Validation(err) => Self::InvalidInvoice { err },
             _ => Self::Generic {
                 err: value.to_string(),
             },
@@ -305,6 +274,17 @@ impl From<LnUrlError> for LnUrlWithdrawError {
     }
 }
 
+impl From<LnUrlError> for LnUrlWithdrawError {
+    fn from(value: LnUrlError) -> Self {
+        match value {
+            LnUrlError::Generic(err) => Self::Generic { err },
+            LnUrlError::InvalidUri(err) => Self::InvalidUri { err },
+            LnUrlError::InvalidInvoice(err) => err.into(),
+            LnUrlError::ServiceConnectivity(err) => Self::ServiceConnectivity { err },
+        }
+    }
+}
+
 impl From<PersistError> for LnUrlWithdrawError {
     fn from(err: PersistError) -> Self {
         Self::Generic {
@@ -316,15 +296,16 @@ impl From<PersistError> for LnUrlWithdrawError {
 impl From<ReceivePaymentError> for LnUrlWithdrawError {
     fn from(value: ReceivePaymentError) -> Self {
         match value {
+            ReceivePaymentError::Generic { err }
+            | ReceivePaymentError::InvoiceExpired { err }
+            | ReceivePaymentError::InvoiceNoDescription { err }
+            | ReceivePaymentError::InvoicePreimageAlreadyExists { err } => Self::Generic { err },
             ReceivePaymentError::InvalidAmount { err } => Self::InvalidAmount { err },
             ReceivePaymentError::InvalidInvoice { err } => Self::InvalidInvoice { err },
             ReceivePaymentError::ServiceConnectivity { err } => Self::ServiceConnectivity { err },
             ReceivePaymentError::InvoiceNoRoutingHints { err } => {
                 Self::InvoiceNoRoutingHints { err }
             }
-            _ => Self::Generic {
-                err: value.to_string(),
-            },
         }
     }
 }
@@ -332,10 +313,8 @@ impl From<ReceivePaymentError> for LnUrlWithdrawError {
 impl From<SdkError> for LnUrlWithdrawError {
     fn from(value: SdkError) -> Self {
         match value {
+            SdkError::Generic { err } => Self::Generic { err },
             SdkError::ServiceConnectivity { err } => Self::ServiceConnectivity { err },
-            _ => Self::Generic {
-                err: value.to_string(),
-            },
         }
     }
 }
@@ -372,10 +351,8 @@ impl From<anyhow::Error> for ReceiveOnchainError {
 impl From<SdkError> for ReceiveOnchainError {
     fn from(value: SdkError) -> Self {
         match value {
+            SdkError::Generic { err } => Self::Generic { err },
             SdkError::ServiceConnectivity { err } => Self::ServiceConnectivity { err },
-            _ => Self::Generic {
-                err: value.to_string(),
-            },
         }
     }
 }
@@ -383,9 +360,7 @@ impl From<SdkError> for ReceiveOnchainError {
 impl From<SwapError> for ReceiveOnchainError {
     fn from(value: SwapError) -> Self {
         match value {
-            SwapError::ServiceConnectivity(err) => Self::ServiceConnectivity {
-                err: err.to_string(),
-            },
+            SwapError::ServiceConnectivity(err) => Self::ServiceConnectivity { err },
             _ => Self::Generic {
                 err: value.to_string(),
             },
@@ -396,7 +371,7 @@ impl From<SwapError> for ReceiveOnchainError {
 impl From<PersistError> for ReceiveOnchainError {
     fn from(err: PersistError) -> Self {
         Self::Generic {
-            err: format!("Error when accessing local DB: {err}"),
+            err: err.to_string(),
         }
     }
 }
@@ -450,9 +425,12 @@ impl From<anyhow::Error> for ReceivePaymentError {
 }
 
 impl From<InvoiceError> for ReceivePaymentError {
-    fn from(err: InvoiceError) -> Self {
-        Self::InvalidInvoice {
-            err: err.to_string(),
+    fn from(value: InvoiceError) -> Self {
+        match value {
+            InvoiceError::Validation(err) => Self::InvalidInvoice { err },
+            _ => Self::Generic {
+                err: value.to_string(),
+            },
         }
     }
 }
@@ -460,18 +438,12 @@ impl From<InvoiceError> for ReceivePaymentError {
 impl From<NodeError> for ReceivePaymentError {
     fn from(value: NodeError) -> Self {
         match value {
-            NodeError::InvoiceExpired(err) => Self::InvoiceExpired {
-                err: err.to_string(),
-            },
-            NodeError::InvoiceNoDescription(err) => Self::InvoiceNoDescription {
-                err: err.to_string(),
-            },
-            NodeError::InvoicePreimageAlreadyExists(err) => Self::InvoicePreimageAlreadyExists {
-                err: err.to_string(),
-            },
-            NodeError::ServiceConnectivity(err) => Self::ServiceConnectivity {
-                err: err.to_string(),
-            },
+            NodeError::InvoiceExpired(err) => Self::InvoiceExpired { err },
+            NodeError::InvoiceNoDescription(err) => Self::InvoiceNoDescription { err },
+            NodeError::InvoicePreimageAlreadyExists(err) => {
+                Self::InvoicePreimageAlreadyExists { err }
+            }
+            NodeError::ServiceConnectivity(err) => Self::ServiceConnectivity { err },
             _ => Self::Generic {
                 err: value.to_string(),
             },
@@ -490,10 +462,8 @@ impl From<PersistError> for ReceivePaymentError {
 impl From<SdkError> for ReceivePaymentError {
     fn from(value: SdkError) -> Self {
         match value {
+            SdkError::Generic { err } => Self::Generic { err },
             SdkError::ServiceConnectivity { err } => Self::ServiceConnectivity { err },
-            _ => Self::Generic {
-                err: value.to_string(),
-            },
         }
     }
 }
@@ -517,6 +487,12 @@ impl SdkError {
             err: err.to_string(),
         }
     }
+
+    pub(crate) fn service_connectivity(err: &str) -> Self {
+        Self::ServiceConnectivity {
+            err: err.to_string(),
+        }
+    }
 }
 
 impl From<anyhow::Error> for SdkError {
@@ -554,9 +530,7 @@ impl From<LnUrlError> for SdkError {
 impl From<NodeError> for SdkError {
     fn from(value: NodeError) -> Self {
         match value {
-            NodeError::ServiceConnectivity(err) => Self::ServiceConnectivity {
-                err: err.to_string(),
-            },
+            NodeError::ServiceConnectivity(err) => Self::ServiceConnectivity { err },
             _ => Self::Generic {
                 err: value.to_string(),
             },
@@ -575,9 +549,7 @@ impl From<PersistError> for SdkError {
 impl From<ReverseSwapError> for SdkError {
     fn from(value: ReverseSwapError) -> Self {
         match value {
-            ReverseSwapError::ServiceConnectivity(err) => Self::ServiceConnectivity {
-                err: err.to_string(),
-            },
+            ReverseSwapError::ServiceConnectivity(err) => Self::ServiceConnectivity { err },
             _ => Self::Generic {
                 err: value.to_string(),
             },
@@ -612,11 +584,19 @@ impl From<tonic::Status> for SdkError {
 impl From<SendPaymentError> for SdkError {
     fn from(value: SendPaymentError) -> Self {
         match value {
-            SendPaymentError::Generic { err } => Self::Generic { err },
-            SendPaymentError::ServiceConnectivity { err } => Self::ServiceConnectivity { err },
-            _ => Self::Generic {
+            SendPaymentError::AlreadyPaid => Self::Generic {
                 err: value.to_string(),
             },
+            SendPaymentError::Generic { err }
+            | SendPaymentError::InvalidAmount { err }
+            | SendPaymentError::InvalidInvoice { err }
+            | SendPaymentError::InvalidNetwork { err }
+            | SendPaymentError::InvoiceExpired { err }
+            | SendPaymentError::PaymentFailed { err }
+            | SendPaymentError::PaymentTimeout { err }
+            | SendPaymentError::RouteNotFound { err }
+            | SendPaymentError::RouteTooExpensive { err } => Self::Generic { err },
+            SendPaymentError::ServiceConnectivity { err } => Self::ServiceConnectivity { err },
         }
     }
 }
@@ -670,15 +650,9 @@ impl From<anyhow::Error> for SendOnchainError {
 impl From<NodeError> for SendOnchainError {
     fn from(value: NodeError) -> Self {
         match value {
-            NodeError::PaymentFailed(err) => Self::PaymentFailed {
-                err: err.to_string(),
-            },
-            NodeError::PaymentTimeout(err) => Self::PaymentTimeout {
-                err: err.to_string(),
-            },
-            NodeError::ServiceConnectivity(err) => Self::ServiceConnectivity {
-                err: err.to_string(),
-            },
+            NodeError::PaymentFailed(err) => Self::PaymentFailed { err },
+            NodeError::PaymentTimeout(err) => Self::PaymentTimeout { err },
+            NodeError::ServiceConnectivity(err) => Self::ServiceConnectivity { err },
             _ => Self::Generic {
                 err: value.to_string(),
             },
@@ -689,10 +663,8 @@ impl From<NodeError> for SendOnchainError {
 impl From<SdkError> for SendOnchainError {
     fn from(value: SdkError) -> Self {
         match value {
+            SdkError::Generic { err } => Self::Generic { err },
             SdkError::ServiceConnectivity { err } => Self::ServiceConnectivity { err },
-            _ => Self::Generic {
-                err: value.to_string(),
-            },
         }
     }
 }
@@ -700,12 +672,10 @@ impl From<SdkError> for SendOnchainError {
 impl From<ReverseSwapError> for SendOnchainError {
     fn from(value: ReverseSwapError) -> Self {
         match value {
-            ReverseSwapError::InvalidDestinationAddress(err) => Self::InvalidDestinationAddress {
-                err: err.to_string(),
-            },
-            ReverseSwapError::ServiceConnectivity(err) => Self::ServiceConnectivity {
-                err: err.to_string(),
-            },
+            ReverseSwapError::InvalidDestinationAddress(err) => {
+                Self::InvalidDestinationAddress { err }
+            }
+            ReverseSwapError::ServiceConnectivity(err) => Self::ServiceConnectivity { err },
             ReverseSwapError::Node(err) => err.into(),
             _ => Self::Generic {
                 err: value.to_string(),
@@ -777,12 +747,9 @@ impl From<anyhow::Error> for SendPaymentError {
 impl From<InvoiceError> for SendPaymentError {
     fn from(value: InvoiceError) -> Self {
         match value {
-            InvoiceError::InvalidNetwork(err) => Self::InvalidNetwork {
-                err: err.to_string(),
-            },
-            _ => Self::InvalidInvoice {
-                err: value.to_string(),
-            },
+            InvoiceError::InvalidNetwork(err) => Self::InvalidNetwork { err },
+            InvoiceError::Validation(err) => Self::InvalidInvoice { err },
+            InvoiceError::Generic(err) => Self::Generic { err },
         }
     }
 }
@@ -790,24 +757,12 @@ impl From<InvoiceError> for SendPaymentError {
 impl From<NodeError> for SendPaymentError {
     fn from(value: NodeError) -> Self {
         match value {
-            NodeError::InvoiceExpired(err) => Self::InvoiceExpired {
-                err: err.to_string(),
-            },
-            NodeError::PaymentFailed(err) => Self::PaymentFailed {
-                err: err.to_string(),
-            },
-            NodeError::PaymentTimeout(err) => Self::PaymentTimeout {
-                err: err.to_string(),
-            },
-            NodeError::RouteNotFound(err) => Self::RouteNotFound {
-                err: err.to_string(),
-            },
-            NodeError::RouteTooExpensive(err) => Self::RouteTooExpensive {
-                err: err.to_string(),
-            },
-            NodeError::ServiceConnectivity(err) => Self::ServiceConnectivity {
-                err: err.to_string(),
-            },
+            NodeError::InvoiceExpired(err) => Self::InvoiceExpired { err },
+            NodeError::PaymentFailed(err) => Self::PaymentFailed { err },
+            NodeError::PaymentTimeout(err) => Self::PaymentTimeout { err },
+            NodeError::RouteNotFound(err) => Self::RouteNotFound { err },
+            NodeError::RouteTooExpensive(err) => Self::RouteTooExpensive { err },
+            NodeError::ServiceConnectivity(err) => Self::ServiceConnectivity { err },
             _ => Self::Generic {
                 err: value.to_string(),
             },
@@ -826,10 +781,8 @@ impl From<PersistError> for SendPaymentError {
 impl From<SdkError> for SendPaymentError {
     fn from(value: SdkError) -> Self {
         match value {
+            SdkError::Generic { err } => Self::Generic { err },
             SdkError::ServiceConnectivity { err } => Self::ServiceConnectivity { err },
-            _ => Self::Generic {
-                err: value.to_string(),
-            },
         }
     }
 }
diff --git a/libs/sdk-core/src/greenlight/error.rs b/libs/sdk-core/src/greenlight/error.rs
index d54fb13df..5a62ba4e3 100644
--- a/libs/sdk-core/src/greenlight/error.rs
+++ b/libs/sdk-core/src/greenlight/error.rs
@@ -4,9 +4,7 @@ use anyhow::{anyhow, Result};
 use regex::Regex;
 use strum_macros::FromRepr;
 
-use crate::{
-    bitcoin::secp256k1, invoice::InvoiceError, node_api::NodeError, persist::error::PersistError,
-};
+use crate::{bitcoin::secp256k1, node_api::NodeError};
 
 #[derive(FromRepr, Debug, PartialEq)]
 #[repr(i16)]
@@ -114,55 +112,43 @@ pub(crate) enum JsonRpcErrCode {
 
 impl From<anyhow::Error> for NodeError {
     fn from(err: anyhow::Error) -> Self {
-        Self::Generic(err)
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<crate::bitcoin::util::address::Error> for NodeError {
     fn from(err: crate::bitcoin::util::address::Error) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<crate::bitcoin::util::bip32::Error> for NodeError {
     fn from(err: crate::bitcoin::util::bip32::Error) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<hex::FromHexError> for NodeError {
     fn from(err: hex::FromHexError) -> Self {
-        Self::Generic(anyhow::Error::new(err))
-    }
-}
-
-impl From<InvoiceError> for NodeError {
-    fn from(err: InvoiceError) -> Self {
-        Self::InvalidInvoice(err)
-    }
-}
-
-impl From<PersistError> for NodeError {
-    fn from(err: PersistError) -> Self {
-        Self::Persistance(err)
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<secp256k1::Error> for NodeError {
     fn from(err: secp256k1::Error) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<serde_json::Error> for NodeError {
     fn from(err: serde_json::Error) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<SystemTimeError> for NodeError {
     fn from(err: SystemTimeError) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
@@ -171,12 +157,12 @@ impl From<tonic::Status> for NodeError {
         match parse_cln_error(status.clone()) {
             Ok(code) => match code {
                 // Pay errors
-                JsonRpcErrCode::PayInvoiceExpired => Self::InvoiceExpired(status.into()),
+                JsonRpcErrCode::PayInvoiceExpired => Self::InvoiceExpired(status.to_string()),
                 JsonRpcErrCode::PayTryOtherRoute | JsonRpcErrCode::PayRouteNotFound => {
-                    Self::RouteNotFound(status.into())
+                    Self::RouteNotFound(status.to_string())
                 }
-                JsonRpcErrCode::PayRouteTooExpensive => Self::RouteTooExpensive(status.into()),
-                JsonRpcErrCode::PayStoppedRetrying => Self::PaymentTimeout(status.into()),
+                JsonRpcErrCode::PayRouteTooExpensive => Self::RouteTooExpensive(status.to_string()),
+                JsonRpcErrCode::PayStoppedRetrying => Self::PaymentTimeout(status.to_string()),
                 JsonRpcErrCode::PayRhashAlreadyUsed
                 | JsonRpcErrCode::PayUnparseableOnion
                 | JsonRpcErrCode::PayDestinationPermFail
@@ -186,24 +172,28 @@ impl From<tonic::Status> for NodeError {
                 | JsonRpcErrCode::PayInvoiceRequestInvalid
                 | JsonRpcErrCode::PayInvoicePreapprovalDeclined
                 | JsonRpcErrCode::PayKeysendPreapprovalDeclined => {
-                    Self::PaymentFailed(status.into())
+                    Self::PaymentFailed(status.to_string())
                 }
                 // Invoice errors
-                JsonRpcErrCode::InvoiceExpiredDuringWait => Self::InvoiceExpired(status.into()),
-                JsonRpcErrCode::InvoiceNoDescription => Self::InvoiceNoDescription(status.into()),
+                JsonRpcErrCode::InvoiceExpiredDuringWait => {
+                    Self::InvoiceExpired(status.to_string())
+                }
+                JsonRpcErrCode::InvoiceNoDescription => {
+                    Self::InvoiceNoDescription(status.to_string())
+                }
                 JsonRpcErrCode::InvoicePreimageAlreadyExists => {
-                    Self::InvoicePreimageAlreadyExists(status.into())
+                    Self::InvoicePreimageAlreadyExists(status.to_string())
                 }
-                _ => Self::Generic(status.into()),
+                _ => Self::Generic(status.to_string()),
             },
-            _ => Self::Generic(status.into()),
+            _ => Self::Generic(status.to_string()),
         }
     }
 }
 
 impl From<TryFromIntError> for NodeError {
     fn from(err: TryFromIntError) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
diff --git a/libs/sdk-core/src/greenlight/node_api.rs b/libs/sdk-core/src/greenlight/node_api.rs
index b5f5f848c..236d7c2a2 100644
--- a/libs/sdk-core/src/greenlight/node_api.rs
+++ b/libs/sdk-core/src/greenlight/node_api.rs
@@ -102,7 +102,7 @@ impl Greenlight {
         // Query for the existing credentials
         let mut parsed_credentials =
             Self::get_node_credentials(config.network, &signer, persister.clone())?
-                .ok_or(NodeError::Generic(anyhow!("No credentials found")));
+                .ok_or(NodeError::generic("No credentials found"));
         if parsed_credentials.is_err() {
             info!("No credentials found, trying to recover existing node");
             parsed_credentials = match Self::recover(config.network, seed.clone()).await {
@@ -122,7 +122,7 @@ impl Greenlight {
                             Ok(credentials)
                         }
                         true => {
-                            return Err(NodeError::RestoreOnly(anyhow!("Node does not exist")));
+                            return Err(NodeError::RestoreOnly("Node does not exist".to_string()));
                         }
                     }
                 }
@@ -140,11 +140,11 @@ impl Greenlight {
                         Greenlight::new(config, seed, creds, persister)
                     }
                     None => {
-                        return Err(NodeError::Generic(anyhow!("Failed to encrypt credentials")));
+                        return Err(NodeError::generic("Failed to encrypt credentials"));
                     }
                 }
             }
-            Err(_) => Err(NodeError::Generic(anyhow!("Failed to get gl credentials"))),
+            Err(_) => Err(NodeError::generic("Failed to get gl credentials")),
         };
         res
     }
@@ -353,12 +353,12 @@ impl Greenlight {
         if gl_client.is_none() {
             let scheduler = Scheduler::new(self.signer.node_id(), self.sdk_config.network.into())
                 .await
-                .map_err(NodeError::ServiceConnectivity)?;
+                .map_err(|e| NodeError::ServiceConnectivity(e.to_string()))?;
             *gl_client = Some(
                 scheduler
                     .schedule(self.tls_config.clone())
                     .await
-                    .map_err(NodeError::ServiceConnectivity)?,
+                    .map_err(|e| NodeError::ServiceConnectivity(e.to_string()))?,
             );
         }
         Ok(gl_client.clone().unwrap())
@@ -369,12 +369,12 @@ impl Greenlight {
         if node_client.is_none() {
             let scheduler = Scheduler::new(self.signer.node_id(), self.sdk_config.network.into())
                 .await
-                .map_err(NodeError::ServiceConnectivity)?;
+                .map_err(|e| NodeError::ServiceConnectivity(e.to_string()))?;
             *node_client = Some(
                 scheduler
                     .schedule(self.tls_config.clone())
                     .await
-                    .map_err(NodeError::ServiceConnectivity)?,
+                    .map_err(|e| NodeError::ServiceConnectivity(e.to_string()))?,
             );
         }
         Ok(node_client.clone().unwrap())
@@ -416,14 +416,13 @@ impl Greenlight {
                 match decrypted_credentials {
                     Some(decrypted_creds) => {
                         let credentials: GreenlightCredentials =
-                            serde_json::from_slice(decrypted_creds.as_slice()).map_err(|e| {
-                                NodeError::Generic(anyhow!("Unable to parse credentials: {e}"))
-                            })?;
+                            serde_json::from_slice(decrypted_creds.as_slice())
+                                .map_err(|_| NodeError::generic("Unable to parse credentials"))?;
                         Ok(Some(credentials))
                     }
-                    None => Err(NodeError::Generic(anyhow!(
-                        "Failed to decrypt credentials, seed doesn't match existing node"
-                    ))),
+                    None => Err(NodeError::generic(
+                        "Failed to decrypt credentials, seed doesn't match existing node",
+                    )),
                 }
             }
             None => Ok(None),
@@ -631,8 +630,8 @@ impl Greenlight {
                 .into_inner()
                 .channels;
 
-            let first_channel = hopchannels.first().ok_or(NodeError::RouteNotFound(anyhow!(
-                "channel not found {}",
+            let first_channel = hopchannels.first().ok_or(NodeError::RouteNotFound(format!(
+                "Channel not found {}",
                 hop.channel.clone()
             )))?;
 
@@ -666,9 +665,10 @@ impl Greenlight {
             None => match payee_node_id.clone() {
                 Some(node_id) => (node_id, max_hops),
                 None => {
-                    return Err(NodeError::RouteNotFound(anyhow!(
+                    return Err(NodeError::RouteNotFound(
                         "No payee node id or last hop hints provided, cannot calculate max amount"
-                    )));
+                            .to_string(),
+                    ));
                 }
             },
         };
@@ -722,7 +722,7 @@ impl Greenlight {
             let chan_id = c
                 .clone()
                 .channel_id
-                .ok_or(NodeError::Generic(anyhow!("Empty channel id")))?;
+                .ok_or(NodeError::generic("Empty channel id"))?;
 
             // First hop is forwarding so no fees and delays.
             let first_edge = PaymentPathEdge {
@@ -783,7 +783,7 @@ impl NodeAPI for Greenlight {
                     .await?
                     .configure(gl_client::pb::GlConfig { close_to_addr })
                     .await
-                    .map_err(|e| NodeError::Generic(anyhow!("Unable to set node config: {}", e)))?;
+                    .map_err(|e| NodeError::Generic(format!("Unable to set node config: {}", e)))?;
             }
             None => {
                 self.get_node_client()
@@ -794,7 +794,7 @@ impl NodeAPI for Greenlight {
                     })
                     .await
                     .map_err(|e| {
-                        NodeError::Generic(anyhow!("Unable to delete node config: {}", e))
+                        NodeError::Generic(format!("Unable to delete node config: {}", e))
                     })?;
             }
         }
@@ -987,12 +987,12 @@ impl NodeAPI for Greenlight {
 
         let amount_to_pay_msat = match invoice.amount_msat {
             Some(amount) => Ok(amount),
-            None => Err(NodeError::Generic(anyhow!("Invoice has no amount"))),
+            None => Err(NodeError::generic("Invoice has no amount")),
         }?;
 
         if amount_to_pay_msat > total_msat {
-            return Err(NodeError::RouteNotFound(anyhow!(
-                "amount too high, max amount is {} msat",
+            return Err(NodeError::RouteNotFound(format!(
+                "Amount too high, max amount is {} msat",
                 total_msat
             )));
         }
@@ -1158,7 +1158,8 @@ impl NodeAPI for Greenlight {
             .get_node_client()
             .await?
             .getinfo(cln::GetinfoRequest {})
-            .await?
+            .await
+            .map_err(|e| NodeError::ServiceConnectivity(e.to_string()))?
             .into_inner();
         Ok(hex::encode(node_info.id))
     }
@@ -1227,9 +1228,7 @@ impl NodeAPI for Greenlight {
             + witness_input_size * txins.len() as u64;
         let fee: u64 = tx_weight * req.sat_per_vbyte as u64 / WITNESS_SCALE_FACTOR as u64;
         if fee >= amount_sat {
-            return Err(NodeError::Generic(anyhow!(
-                "Insufficient funds to pay fees"
-            )));
+            return Err(NodeError::generic("Insufficient funds to pay fees"));
         }
 
         return Ok(PrepareRedeemOnchainFundsResponse {
@@ -1943,7 +1942,7 @@ impl TryFrom<cln::ListinvoicesInvoices> for Payment {
         let ln_invoice = invoice
             .bolt11
             .as_ref()
-            .ok_or(InvoiceError::Generic(anyhow!("No bolt11 invoice")))
+            .ok_or(InvoiceError::generic("No bolt11 invoice"))
             .and_then(|b| parse_invoice(b))?;
         Ok(Payment {
             id: hex::encode(invoice.payment_hash.clone()),
@@ -1998,7 +1997,7 @@ impl TryFrom<cln::ListpaysPays> for Payment {
         let ln_invoice = payment
             .bolt11
             .as_ref()
-            .ok_or(InvoiceError::Generic(anyhow!("No bolt11 invoice")))
+            .ok_or(InvoiceError::generic("No bolt11 invoice"))
             .and_then(|b| parse_invoice(b));
         let payment_amount = payment
             .amount_msat
diff --git a/libs/sdk-core/src/input_parser.rs b/libs/sdk-core/src/input_parser.rs
index c497fd011..c564e70a2 100644
--- a/libs/sdk-core/src/input_parser.rs
+++ b/libs/sdk-core/src/input_parser.rs
@@ -9,6 +9,9 @@ use serde::Serialize;
 use crate::bitcoin::bech32;
 use crate::bitcoin::bech32::FromBase32;
 use crate::ensure_sdk;
+use crate::error::SdkError;
+use crate::error::SdkError::ServiceConnectivity;
+use crate::error::SdkResult;
 use crate::input_parser::InputType::*;
 use crate::input_parser::LnUrlRequestData::*;
 use crate::invoice::{parse_invoice, LNInvoice};
@@ -230,14 +233,20 @@ pub async fn parse(input: &str) -> Result<InputType> {
     Err(anyhow!("Unrecognized input type"))
 }
 
-pub(crate) async fn post_and_log_response(url: &str, body: Option<String>) -> Result<String> {
+pub(crate) async fn post_and_log_response(url: &str, body: Option<String>) -> SdkResult<String> {
     debug!("Making POST request to: {url}");
 
     let mut req = get_reqwest_client()?.post(url);
     if let Some(body) = body {
         req = req.body(body);
     }
-    let raw_body = req.send().await?.text().await?;
+    let raw_body = req
+        .send()
+        .await
+        .map_err(|e| SdkError::ServiceConnectivity { err: e.to_string() })?
+        .text()
+        .await
+        .map_err(|e| SdkError::ServiceConnectivity { err: e.to_string() })?;
     debug!("Received raw response body: {raw_body}");
 
     Ok(raw_body)
@@ -246,17 +255,24 @@ pub(crate) async fn post_and_log_response(url: &str, body: Option<String>) -> Re
 /// Makes a GET request to the specified `url` and logs on DEBUG:
 /// - the URL
 /// - the raw response body
-pub(crate) async fn get_and_log_response(url: &str) -> Result<String> {
+pub(crate) async fn get_and_log_response(url: &str) -> SdkResult<String> {
     debug!("Making GET request to: {url}");
 
-    let raw_body = get_reqwest_client()?.get(url).send().await?.text().await?;
+    let raw_body = get_reqwest_client()?
+        .get(url)
+        .send()
+        .await
+        .map_err(|e| SdkError::ServiceConnectivity { err: e.to_string() })?
+        .text()
+        .await
+        .map_err(|e| SdkError::ServiceConnectivity { err: e.to_string() })?;
     debug!("Received raw response body: {raw_body}");
 
     Ok(raw_body)
 }
 
 /// Wrapper around [get_and_log_response] that, in addition, parses the payload into an expected type
-pub(crate) async fn get_parse_and_log_response<T>(url: &str) -> Result<T>
+pub(crate) async fn get_parse_and_log_response<T>(url: &str) -> SdkResult<T>
 where
     for<'a> T: serde::de::Deserialize<'a>,
 {
@@ -338,20 +354,20 @@ fn lnurl_decode(encoded: &str) -> LnUrlResult<(String, String, Option<String>)>
             let decoded = String::from_utf8(Vec::from_base32(&payload)?)?;
 
             let url = reqwest::Url::parse(&decoded)
-                .map_err(|e| super::lnurl::error::LnUrlError::InvalidUri(anyhow::Error::new(e)))?;
+                .map_err(|e| super::lnurl::error::LnUrlError::InvalidUri(e.to_string()))?;
             let domain = url.domain().ok_or_else(|| {
-                super::lnurl::error::LnUrlError::InvalidUri(anyhow!("Could not determine domain"))
+                super::lnurl::error::LnUrlError::invalid_uri("Could not determine domain")
             })?;
 
             if url.scheme() == "http" && !domain.ends_with(".onion") {
-                return Err(super::lnurl::error::LnUrlError::Generic(anyhow!(
-                    "HTTP scheme only allowed for onion domains"
-                )));
+                return Err(super::lnurl::error::LnUrlError::generic(
+                    "HTTP scheme only allowed for onion domains",
+                ));
             }
             if url.scheme() == "https" && domain.ends_with(".onion") {
-                return Err(super::lnurl::error::LnUrlError::Generic(anyhow!(
-                    "HTTPS scheme not allowed for onion domains"
-                )));
+                return Err(super::lnurl::error::LnUrlError::generic(
+                    "HTTPS scheme not allowed for onion domains",
+                ));
             }
 
             Ok((domain.into(), decoded, None))
@@ -372,13 +388,13 @@ fn lnurl_decode(encoded: &str) -> LnUrlResult<(String, String, Option<String>)>
             }
 
             let url = reqwest::Url::parse(&encoded)
-                .map_err(|e| super::lnurl::error::LnUrlError::InvalidUri(anyhow::Error::new(e)))?;
+                .map_err(|e| super::lnurl::error::LnUrlError::InvalidUri(e.to_string()))?;
             let domain = url.domain().ok_or_else(|| {
-                super::lnurl::error::LnUrlError::InvalidUri(anyhow!("Could not determine domain"))
+                super::lnurl::error::LnUrlError::invalid_uri("Could not determine domain")
             })?;
             ensure_sdk!(
                 supported_prefixes.contains(&url.scheme()),
-                super::lnurl::error::LnUrlError::Generic(anyhow!("Invalid prefix scheme"))
+                super::lnurl::error::LnUrlError::generic("Invalid prefix scheme")
             );
 
             let scheme = url.scheme();
@@ -393,11 +409,11 @@ fn lnurl_decode(encoded: &str) -> LnUrlResult<(String, String, Option<String>)>
 }
 
 /// Creates an HTTP client with a built-in connection timeout
-pub(crate) fn get_reqwest_client() -> Result<reqwest::Client> {
+pub(crate) fn get_reqwest_client() -> SdkResult<reqwest::Client> {
     reqwest::Client::builder()
         .timeout(Duration::from_secs(30))
         .build()
-        .map_err(Into::into)
+        .map_err(|e| ServiceConnectivity { err: e.to_string() })
 }
 
 /// Different kinds of inputs supported by [parse], including any relevant details extracted from the input
diff --git a/libs/sdk-core/src/invoice.rs b/libs/sdk-core/src/invoice.rs
index e47638938..b23d1f5e6 100644
--- a/libs/sdk-core/src/invoice.rs
+++ b/libs/sdk-core/src/invoice.rs
@@ -1,7 +1,6 @@
 use std::str::FromStr;
 use std::time::{SystemTimeError, UNIX_EPOCH};
 
-use anyhow::anyhow;
 use hex::ToHex;
 use regex::Regex;
 use serde::{Deserialize, Serialize};
@@ -16,49 +15,63 @@ pub type InvoiceResult<T, E = InvoiceError> = Result<T, E>;
 
 #[derive(Debug, thiserror::Error)]
 pub enum InvoiceError {
-    #[error("Generic: {0}")]
-    Generic(#[from] anyhow::Error),
+    #[error("{0}")]
+    Generic(String),
 
-    #[error("Invalid network: {0}")]
-    InvalidNetwork(anyhow::Error),
+    #[error("{0}")]
+    InvalidNetwork(String),
 
-    #[error("Validation: {0}")]
-    Validation(anyhow::Error),
+    #[error("{0}")]
+    Validation(String),
+}
+
+impl InvoiceError {
+    pub(crate) fn generic(err: &str) -> Self {
+        Self::Generic(err.to_string())
+    }
+
+    pub(crate) fn invalid_network(err: &str) -> Self {
+        Self::InvalidNetwork(err.to_string())
+    }
+
+    pub(crate) fn validation(err: &str) -> Self {
+        Self::Validation(err.to_string())
+    }
 }
 
 impl From<crate::lightning_invoice::CreationError> for InvoiceError {
     fn from(err: crate::lightning_invoice::CreationError) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<crate::lightning_invoice::Bolt11ParseError> for InvoiceError {
     fn from(err: crate::lightning_invoice::Bolt11ParseError) -> Self {
-        Self::Validation(anyhow::Error::new(err))
+        Self::Validation(err.to_string())
     }
 }
 
 impl From<crate::lightning_invoice::Bolt11SemanticError> for InvoiceError {
     fn from(err: crate::lightning_invoice::Bolt11SemanticError) -> Self {
-        Self::Validation(anyhow::Error::new(err))
+        Self::Validation(err.to_string())
     }
 }
 
 impl From<regex::Error> for InvoiceError {
     fn from(err: regex::Error) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<secp256k1::Error> for InvoiceError {
     fn from(err: secp256k1::Error) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<SystemTimeError> for InvoiceError {
     fn from(err: SystemTimeError) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
@@ -224,18 +237,16 @@ pub fn add_routing_hints(
 pub fn validate_network(invoice: LNInvoice, network: Network) -> InvoiceResult<()> {
     match invoice.network == network {
         true => Ok(()),
-        false => Err(InvoiceError::InvalidNetwork(anyhow!(
-            "Invoice network does not match config"
-        ))),
+        false => Err(InvoiceError::invalid_network(
+            "Invoice network does not match config",
+        )),
     }
 }
 
 /// Parse a BOLT11 payment request and return a structure contains the parsed fields.
 pub fn parse_invoice(bolt11: &str) -> InvoiceResult<LNInvoice> {
     if bolt11.trim().is_empty() {
-        return Err(InvoiceError::Validation(anyhow!(
-            "bolt11 is an empty string"
-        )));
+        return Err(InvoiceError::validation("Bolt11 is an empty string"));
     }
     let re = Regex::new(r"(?i)^lightning:")?;
     let bolt11 = re.replace_all(bolt11, "");
diff --git a/libs/sdk-core/src/lnurl/auth.rs b/libs/sdk-core/src/lnurl/auth.rs
index ef7050564..0f64037fa 100644
--- a/libs/sdk-core/src/lnurl/auth.rs
+++ b/libs/sdk-core/src/lnurl/auth.rs
@@ -1,7 +1,6 @@
 use std::str::FromStr;
 use std::sync::Arc;
 
-use anyhow::anyhow;
 use reqwest::Url;
 
 use crate::bitcoin::hashes::{hex::ToHex, sha256, Hash, HashEngine, Hmac, HmacEngine};
@@ -21,19 +20,18 @@ pub(crate) async fn perform_lnurl_auth(
     node_api: Arc<dyn NodeAPI>,
     req_data: LnUrlAuthRequestData,
 ) -> LnUrlResult<LnUrlCallbackStatus> {
-    let url =
-        Url::from_str(&req_data.url).map_err(|e| LnUrlError::InvalidUri(anyhow::Error::new(e)))?;
+    let url = Url::from_str(&req_data.url).map_err(|e| LnUrlError::InvalidUri(e.to_string()))?;
     let linking_keys = derive_linking_keys(node_api, url)?;
 
     let k1_to_sign = Message::from_slice(
         &hex::decode(req_data.k1)
-            .map_err(|e| LnUrlError::Generic(anyhow!("Error decoding k1: {e}")))?,
+            .map_err(|e| LnUrlError::Generic(format!("Error decoding k1: {e}")))?,
     )?;
     let sig = Secp256k1::new().sign_ecdsa(&k1_to_sign, &linking_keys.secret_key());
 
     // <LNURL_hostname_and_path>?<LNURL_existing_query_parameters>&sig=<hex(sign(utf8ToBytes(k1), linkingPrivKey))>&key=<hex(linkingKey)>
     let mut callback_url =
-        Url::from_str(&req_data.url).map_err(|e| LnUrlError::InvalidUri(anyhow::Error::new(e)))?;
+        Url::from_str(&req_data.url).map_err(|e| LnUrlError::InvalidUri(e.to_string()))?;
     callback_url
         .query_pairs_mut()
         .append_pair("sig", &sig.serialize_der().to_hex());
@@ -43,22 +41,22 @@ pub(crate) async fn perform_lnurl_auth(
 
     get_parse_and_log_response(callback_url.as_ref())
         .await
-        .map_err(LnUrlError::ServiceConnectivity)
+        .map_err(|e| LnUrlError::ServiceConnectivity(e.to_string()))
 }
 
 pub(crate) fn validate_request(
     domain: String,
     lnurl_endpoint: String,
 ) -> LnUrlResult<LnUrlAuthRequestData> {
-    let query = Url::from_str(&lnurl_endpoint)
-        .map_err(|e| LnUrlError::InvalidUri(anyhow::Error::new(e)))?;
+    let query =
+        Url::from_str(&lnurl_endpoint).map_err(|e| LnUrlError::InvalidUri(e.to_string()))?;
     let query_pairs = query.query_pairs();
 
     let k1 = query_pairs
         .into_iter()
         .find(|(key, _)| key == "k1")
         .map(|(_, v)| v.to_string())
-        .ok_or(LnUrlError::Generic(anyhow!("LNURL-auth k1 arg not found")))?;
+        .ok_or(LnUrlError::generic("LNURL-auth k1 arg not found"))?;
 
     let maybe_action = query_pairs
         .into_iter()
@@ -66,18 +64,16 @@ pub(crate) fn validate_request(
         .map(|(_, v)| v.to_string());
 
     let k1_bytes =
-        hex::decode(&k1).map_err(|e| LnUrlError::Generic(anyhow!("Error decoding k1: {e}")))?;
+        hex::decode(&k1).map_err(|e| LnUrlError::Generic(format!("Error decoding k1: {e}")))?;
     if k1_bytes.len() != 32 {
-        return Err(LnUrlError::Generic(anyhow!(
-            "LNURL-auth k1 is of unexpected length"
-        )));
+        return Err(LnUrlError::generic("LNURL-auth k1 is of unexpected length"));
     }
 
     if let Some(action) = &maybe_action {
         if !["register", "login", "link", "auth"].contains(&action.as_str()) {
-            return Err(LnUrlError::Generic(anyhow!(
-                "LNURL-auth action is of unexpected type"
-            )));
+            return Err(LnUrlError::generic(
+                "LNURL-auth action is of unexpected type",
+            ));
         }
     }
 
@@ -99,9 +95,9 @@ fn hmac_sha256(key: &[u8], input: &[u8]) -> Hmac<sha256::Hash> {
 ///
 /// https://github.com/lnurl/luds/blob/luds/05.md
 fn derive_linking_keys(node_api: Arc<dyn NodeAPI>, url: Url) -> LnUrlResult<KeyPair> {
-    let domain = url.domain().ok_or(LnUrlError::InvalidUri(anyhow!(
-        "Could not determine domain"
-    )))?;
+    let domain = url
+        .domain()
+        .ok_or(LnUrlError::invalid_uri("Could not determine domain"))?;
 
     // m/138'/0
     let hashing_key = node_api.derive_bip32_key(vec![
diff --git a/libs/sdk-core/src/lnurl/error.rs b/libs/sdk-core/src/lnurl/error.rs
index 2ff6e3dc1..4169b147a 100644
--- a/libs/sdk-core/src/lnurl/error.rs
+++ b/libs/sdk-core/src/lnurl/error.rs
@@ -1,7 +1,5 @@
 use std::{array::TryFromSliceError, string::FromUtf8Error};
 
-use anyhow::anyhow;
-
 use crate::bitcoin::{bech32, secp256k1, util::bip32};
 use crate::{invoice::InvoiceError, node_api::NodeError};
 
@@ -9,79 +7,89 @@ pub type LnUrlResult<T, E = LnUrlError> = Result<T, E>;
 
 #[derive(Debug, thiserror::Error)]
 pub enum LnUrlError {
-    #[error("Generic: {0}")]
-    Generic(#[from] anyhow::Error),
+    #[error("{0}")]
+    Generic(String),
 
     #[error(transparent)]
     InvalidInvoice(#[from] InvoiceError),
 
-    #[error("Invalid uri: {0}")]
-    InvalidUri(anyhow::Error),
+    #[error("{0}")]
+    InvalidUri(String),
+
+    #[error("{0}")]
+    ServiceConnectivity(String),
+}
+
+impl LnUrlError {
+    pub(crate) fn generic(err: &str) -> Self {
+        Self::Generic(err.to_string())
+    }
 
-    #[error("Service connectivity: {0}")]
-    ServiceConnectivity(anyhow::Error),
+    pub(crate) fn invalid_uri(err: &str) -> Self {
+        Self::InvalidUri(err.to_string())
+    }
 }
 
 impl From<aes::cipher::InvalidLength> for LnUrlError {
     fn from(err: aes::cipher::InvalidLength) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<aes::cipher::block_padding::UnpadError> for LnUrlError {
     fn from(err: aes::cipher::block_padding::UnpadError) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<base64::DecodeError> for LnUrlError {
     fn from(err: base64::DecodeError) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<bip32::Error> for LnUrlError {
     fn from(err: bip32::Error) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<bech32::Error> for LnUrlError {
     fn from(err: bech32::Error) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<FromUtf8Error> for LnUrlError {
     fn from(err: FromUtf8Error) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<NodeError> for LnUrlError {
-    fn from(err: NodeError) -> Self {
-        match err {
+    fn from(value: NodeError) -> Self {
+        match value {
             NodeError::InvalidInvoice(err) => Self::InvalidInvoice(err),
             NodeError::ServiceConnectivity(err) => Self::ServiceConnectivity(err),
-            _ => Self::Generic(anyhow!(err.to_string())),
+            _ => Self::Generic(value.to_string()),
         }
     }
 }
 
 impl From<secp256k1::Error> for LnUrlError {
     fn from(err: secp256k1::Error) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<serde_json::Error> for LnUrlError {
     fn from(err: serde_json::Error) -> Self {
-        Self::ServiceConnectivity(anyhow::Error::new(err))
+        Self::ServiceConnectivity(err.to_string())
     }
 }
 
 impl From<TryFromSliceError> for LnUrlError {
     fn from(err: TryFromSliceError) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
diff --git a/libs/sdk-core/src/lnurl/mod.rs b/libs/sdk-core/src/lnurl/mod.rs
index f733e81be..c7f795626 100644
--- a/libs/sdk-core/src/lnurl/mod.rs
+++ b/libs/sdk-core/src/lnurl/mod.rs
@@ -16,14 +16,14 @@ pub(crate) fn maybe_replace_host_with_mockito_test_host(
     use self::error::LnUrlError;
 
     let server = crate::input_parser::tests::MOCK_HTTP_SERVER.lock().unwrap();
-    let mockito_endpoint_url = reqwest::Url::parse(&server.url())
-        .map_err(|e| LnUrlError::InvalidUri(anyhow::Error::new(e)))?;
-    let mut parsed_lnurl_endpoint = reqwest::Url::parse(&lnurl_endpoint)
-        .map_err(|e| LnUrlError::InvalidUri(anyhow::Error::new(e)))?;
+    let mockito_endpoint_url =
+        reqwest::Url::parse(&server.url()).map_err(|e| LnUrlError::InvalidUri(e.to_string()))?;
+    let mut parsed_lnurl_endpoint =
+        reqwest::Url::parse(&lnurl_endpoint).map_err(|e| LnUrlError::InvalidUri(e.to_string()))?;
 
     parsed_lnurl_endpoint
         .set_host(mockito_endpoint_url.host_str())
-        .map_err(|e| LnUrlError::InvalidUri(anyhow::Error::new(e)))?;
+        .map_err(|e| LnUrlError::InvalidUri(e.to_string()))?;
     let _ = parsed_lnurl_endpoint.set_scheme(mockito_endpoint_url.scheme());
     let _ = parsed_lnurl_endpoint.set_port(mockito_endpoint_url.port());
 
diff --git a/libs/sdk-core/src/lnurl/pay.rs b/libs/sdk-core/src/lnurl/pay.rs
index c18baf504..00cd18e85 100644
--- a/libs/sdk-core/src/lnurl/pay.rs
+++ b/libs/sdk-core/src/lnurl/pay.rs
@@ -3,7 +3,6 @@ use crate::lnurl::maybe_replace_host_with_mockito_test_host;
 use crate::lnurl::pay::model::{CallbackResponse, SuccessAction, ValidatedCallbackResponse};
 use crate::{ensure_sdk, input_parser::*};
 use crate::{LnUrlErrorData, Network};
-use anyhow::anyhow;
 use std::str::FromStr;
 
 use super::error::{LnUrlError, LnUrlResult};
@@ -32,7 +31,7 @@ pub(crate) async fn validate_lnurl_pay(
     let callback_url = build_pay_callback_url(user_amount_msat, &comment, &req_data)?;
     let callback_resp_text = get_and_log_response(&callback_url)
         .await
-        .map_err(LnUrlError::ServiceConnectivity)?;
+        .map_err(|e| LnUrlError::ServiceConnectivity(e.to_string()))?;
 
     if let Ok(err) = serde_json::from_str::<LnUrlErrorData>(&callback_resp_text) {
         Ok(ValidatedCallbackResponse::EndpointError { data: err })
@@ -60,7 +59,7 @@ fn build_pay_callback_url(
 ) -> LnUrlResult<String> {
     let amount_msat = user_amount_msat.to_string();
     let mut url = reqwest::Url::from_str(&data.callback)
-        .map_err(|e| LnUrlError::InvalidUri(anyhow::Error::new(e)))?;
+        .map_err(|e| LnUrlError::InvalidUri(e.to_string()))?;
 
     url.query_pairs_mut().append_pair("amount", &amount_msat);
     if let Some(comment) = user_comment {
@@ -81,21 +80,21 @@ fn validate_user_input(
 ) -> LnUrlResult<()> {
     ensure_sdk!(
         user_amount_msat >= condition_min_amount_msat,
-        LnUrlError::Generic(anyhow!("Amount is smaller than the minimum allowed"))
+        LnUrlError::generic("Amount is smaller than the minimum allowed")
     );
 
     ensure_sdk!(
         user_amount_msat <= condition_max_amount_msat,
-        LnUrlError::Generic(anyhow!("Amount is bigger than the maximum allowed"))
+        LnUrlError::generic("Amount is bigger than the maximum allowed")
     );
 
     match comment {
         None => Ok(()),
         Some(msg) => match msg.len() <= condition_max_comment_len as usize {
             true => Ok(()),
-            false => Err(LnUrlError::Generic(anyhow!(
-                "Comment is longer than the maximum allowed comment length"
-            ))),
+            false => Err(LnUrlError::generic(
+                "Comment is longer than the maximum allowed comment length",
+            )),
         },
     }
 }
@@ -106,14 +105,14 @@ fn validate_invoice(user_amount_msat: u64, bolt11: &str, network: Network) -> Ln
     validate_network(invoice.clone(), network)?;
 
     match invoice.amount_msat {
-        None => Err(LnUrlError::Generic(anyhow!(
-            "Amount is bigger than the maximum allowed"
-        ))),
+        None => Err(LnUrlError::generic(
+            "Amount is bigger than the maximum allowed",
+        )),
         Some(invoice_amount_msat) => match invoice_amount_msat == user_amount_msat {
             true => Ok(()),
-            false => Err(LnUrlError::Generic(anyhow!(
-                "Invoice amount is different than the user chosen amount"
-            ))),
+            false => Err(LnUrlError::generic(
+                "Invoice amount is different than the user chosen amount",
+            )),
         },
     }
 }
@@ -124,7 +123,7 @@ pub(crate) mod model {
     use crate::{ensure_sdk, input_parser::*, Payment};
 
     use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
-    use anyhow::{anyhow, Result};
+    use anyhow::Result;
     use serde::{Deserialize, Serialize};
 
     pub(crate) enum ValidatedCallbackResponse {
@@ -253,23 +252,23 @@ pub(crate) mod model {
         pub fn validate(&self) -> LnUrlResult<()> {
             ensure_sdk!(
                 self.description.len() <= 144,
-                LnUrlError::Generic(anyhow!(
+                LnUrlError::generic(
                     "AES action description length is larger than the maximum allowed"
-                ))
+                )
             );
 
             ensure_sdk!(
                 self.ciphertext.len() <= 4096,
-                LnUrlError::Generic(anyhow!(
+                LnUrlError::generic(
                     "AES action ciphertext length is larger than the maximum allowed"
-                ))
+                )
             );
 
             base64::decode(&self.ciphertext)?;
 
             ensure_sdk!(
                 self.iv.len() == 24,
-                LnUrlError::Generic(anyhow!("AES action iv has unexpected length"))
+                LnUrlError::generic("AES action iv has unexpected length")
             );
 
             base64::decode(&self.iv)?;
@@ -315,9 +314,9 @@ pub(crate) mod model {
         pub fn validate(&self) -> LnUrlResult<()> {
             match self.message.len() <= 144 {
                 true => Ok(()),
-                false => Err(LnUrlError::Generic(anyhow!(
-                    "Success action message is longer than the maximum allowed length"
-                ))),
+                false => Err(LnUrlError::generic(
+                    "Success action message is longer than the maximum allowed length",
+                )),
             }
         }
     }
@@ -326,28 +325,28 @@ pub(crate) mod model {
         pub fn validate(&self, data: &LnUrlPayRequestData) -> LnUrlResult<()> {
             match self.description.len() <= 144 {
                 true => Ok(()),
-                false => Err(LnUrlError::Generic(anyhow!(
-                    "Success action description is longer than the maximum allowed length"
-                ))),
+                false => Err(LnUrlError::generic(
+                    "Success action description is longer than the maximum allowed length",
+                )),
             }
             .and_then(|_| {
                 let req_url = reqwest::Url::parse(&data.callback)
-                    .map_err(|e| LnUrlError::InvalidUri(anyhow::Error::new(e)))?;
+                    .map_err(|e| LnUrlError::InvalidUri(e.to_string()))?;
                 let req_domain = req_url.domain().ok_or_else(|| {
-                    LnUrlError::InvalidUri(anyhow!("Could not determine callback domain"))
+                    LnUrlError::invalid_uri("Could not determine callback domain")
                 })?;
 
                 let action_res_url = reqwest::Url::parse(&self.url)
-                    .map_err(|e| LnUrlError::InvalidUri(anyhow::Error::new(e)))?;
+                    .map_err(|e| LnUrlError::InvalidUri(e.to_string()))?;
                 let action_res_domain = action_res_url.domain().ok_or_else(|| {
-                    LnUrlError::InvalidUri(anyhow!("Could not determine Success Action URL domain"))
+                    LnUrlError::invalid_uri("Could not determine Success Action URL domain")
                 })?;
 
                 match req_domain == action_res_domain {
                     true => Ok(()),
-                    false => Err(LnUrlError::Generic(anyhow!(
-                        "Success Action URL has different domain than the callback domain"
-                    ))),
+                    false => Err(LnUrlError::generic(
+                        "Success Action URL has different domain than the callback domain",
+                    )),
                 }
             })
         }
diff --git a/libs/sdk-core/src/lnurl/withdraw.rs b/libs/sdk-core/src/lnurl/withdraw.rs
index 438d1613c..7de5d51d8 100644
--- a/libs/sdk-core/src/lnurl/withdraw.rs
+++ b/libs/sdk-core/src/lnurl/withdraw.rs
@@ -6,7 +6,6 @@ use crate::{
     ensure_sdk, lnurl::*, LnUrlCallbackStatus, LnUrlWithdrawResult, LnUrlWithdrawSuccessData,
 };
 use crate::{LNInvoice, LnUrlWithdrawRequestData};
-use anyhow::anyhow;
 
 /// Validates invoice and performs the second and last step of LNURL-withdraw, as per
 /// <https://github.com/lnurl/luds/blob/luds/03.md>
@@ -20,28 +19,28 @@ pub(crate) async fn validate_lnurl_withdraw(
     req_data: LnUrlWithdrawRequestData,
     invoice: LNInvoice,
 ) -> LnUrlResult<LnUrlWithdrawResult> {
-    let amount_msat = invoice.amount_msat.ok_or(LnUrlError::Generic(anyhow!(
-        "Expected invoice amount, but found none"
-    )))?;
+    let amount_msat = invoice.amount_msat.ok_or(LnUrlError::generic(
+        "Expected invoice amount, but found none",
+    ))?;
 
     ensure_sdk!(
         amount_msat >= req_data.min_withdrawable,
-        LnUrlError::Generic(anyhow!(
+        LnUrlError::generic(
             "Amount is smaller than the minimum allowed by the LNURL-withdraw endpoint"
-        ))
+        )
     );
     ensure_sdk!(
         amount_msat <= req_data.max_withdrawable,
-        LnUrlError::Generic(anyhow!(
+        LnUrlError::generic(
             "Amount is bigger than the maximum allowed by the LNURL-withdraw endpoint"
-        ))
+        )
     );
 
     // Send invoice to the LNURL-w endpoint via the callback
     let callback_url = build_withdraw_callback_url(&req_data, &invoice)?;
     let callback_res: LnUrlCallbackStatus = get_parse_and_log_response(&callback_url)
         .await
-        .map_err(LnUrlError::ServiceConnectivity)?;
+        .map_err(|e| LnUrlError::ServiceConnectivity(e.to_string()))?;
     let withdraw_status = match callback_res {
         LnUrlCallbackStatus::Ok => LnUrlWithdrawResult::Ok {
             data: LnUrlWithdrawSuccessData { invoice },
@@ -57,7 +56,7 @@ fn build_withdraw_callback_url(
     invoice: &LNInvoice,
 ) -> LnUrlResult<String> {
     let mut url = reqwest::Url::from_str(&req_data.callback)
-        .map_err(|e| LnUrlError::InvalidUri(anyhow::Error::new(e)))?;
+        .map_err(|e| LnUrlError::InvalidUri(e.to_string()))?;
 
     url.query_pairs_mut().append_pair("k1", &req_data.k1);
     url.query_pairs_mut().append_pair("pr", &invoice.bolt11);
diff --git a/libs/sdk-core/src/models.rs b/libs/sdk-core/src/models.rs
index dcb440053..ef9f0956e 100644
--- a/libs/sdk-core/src/models.rs
+++ b/libs/sdk-core/src/models.rs
@@ -278,9 +278,9 @@ impl FullReverseSwapInfo {
         // Validate if received invoice has the same amount as requested by the user
         let amount_from_invoice_msat = inv.amount_milli_satoshis().unwrap_or_default();
         match amount_from_invoice_msat == expected_amount_msat {
-            false => Err(ReverseSwapError::UnexpectedInvoiceAmount(anyhow!(
-                "Does not match the request"
-            ))),
+            false => Err(ReverseSwapError::unexpected_invoice_amount(
+                "Does not match the request",
+            )),
             true => Ok(()),
         }
     }
@@ -298,9 +298,9 @@ impl FullReverseSwapInfo {
         let preimage_hash_from_invoice = inv.payment_hash();
         let preimage_hash_from_req = &self.get_preimage_hash();
         match preimage_hash_from_invoice == preimage_hash_from_req {
-            false => Err(ReverseSwapError::UnexpectedPaymentHash(anyhow!(
-                "Does not match the request"
-            ))),
+            false => Err(ReverseSwapError::unexpected_payment_hash(
+                "Does not match the request",
+            )),
             true => Ok(()),
         }
     }
diff --git a/libs/sdk-core/src/node_api.rs b/libs/sdk-core/src/node_api.rs
index 0aa102ba8..8e544563e 100644
--- a/libs/sdk-core/src/node_api.rs
+++ b/libs/sdk-core/src/node_api.rs
@@ -19,41 +19,47 @@ pub type NodeResult<T, E = NodeError> = Result<T, E>;
 
 #[derive(Debug, thiserror::Error)]
 pub enum NodeError {
-    #[error("Generic: {0}")]
-    Generic(anyhow::Error),
+    #[error("{0}")]
+    Generic(String),
 
     #[error(transparent)]
-    InvalidInvoice(InvoiceError),
+    InvalidInvoice(#[from] InvoiceError),
 
-    #[error("Invoice expired: {0}")]
-    InvoiceExpired(anyhow::Error),
+    #[error("{0}")]
+    InvoiceExpired(String),
 
-    #[error("Invoice no description: {0}")]
-    InvoiceNoDescription(anyhow::Error),
+    #[error("{0}")]
+    InvoiceNoDescription(String),
 
-    #[error("Invoice preimage already exists: {0}")]
-    InvoicePreimageAlreadyExists(anyhow::Error),
+    #[error("{0}")]
+    InvoicePreimageAlreadyExists(String),
 
-    #[error("Payment failed: {0}")]
-    PaymentFailed(anyhow::Error),
+    #[error("{0}")]
+    PaymentFailed(String),
 
-    #[error("Payment timeout: {0}")]
-    PaymentTimeout(anyhow::Error),
+    #[error("{0}")]
+    PaymentTimeout(String),
 
     #[error(transparent)]
-    Persistance(PersistError),
+    Persistance(#[from] PersistError),
 
-    #[error("Restore only: {0}")]
-    RestoreOnly(anyhow::Error),
+    #[error("{0}")]
+    RestoreOnly(String),
 
-    #[error("Route too expensive: {0}")]
-    RouteTooExpensive(anyhow::Error),
+    #[error("{0}")]
+    RouteTooExpensive(String),
 
-    #[error("Route not found: {0}")]
-    RouteNotFound(anyhow::Error),
+    #[error("{0}")]
+    RouteNotFound(String),
 
-    #[error("Service connectivity: {0}")]
-    ServiceConnectivity(anyhow::Error),
+    #[error("{0}")]
+    ServiceConnectivity(String),
+}
+
+impl NodeError {
+    pub(crate) fn generic(err: &str) -> Self {
+        Self::Generic(err.to_string())
+    }
 }
 
 pub struct CreateInvoiceRequest {
diff --git a/libs/sdk-core/src/persist/error.rs b/libs/sdk-core/src/persist/error.rs
index d51472e75..10c36d51f 100644
--- a/libs/sdk-core/src/persist/error.rs
+++ b/libs/sdk-core/src/persist/error.rs
@@ -2,24 +2,48 @@ pub type PersistResult<T, E = PersistError> = Result<T, E>;
 
 #[derive(Debug, thiserror::Error)]
 pub enum PersistError {
-    #[error("Generic: {0}")]
-    Generic(#[from] anyhow::Error),
+    #[error("{0}")]
+    Generic(String),
 
-    #[error("Migration: {0}")]
-    Migration(#[from] rusqlite_migration::Error),
+    #[error("{0}")]
+    Migration(String),
 
-    #[error("SQL: {0}")]
-    Sql(#[from] rusqlite::Error),
+    #[error("Sql persistence: {0}")]
+    Sql(String),
+}
+
+impl PersistError {
+    pub(crate) fn generic(err: &str) -> Self {
+        Self::Generic(err.to_string())
+    }
+}
+
+impl From<anyhow::Error> for PersistError {
+    fn from(err: anyhow::Error) -> Self {
+        Self::Generic(err.to_string())
+    }
 }
 
 impl From<hex::FromHexError> for PersistError {
     fn from(err: hex::FromHexError) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
+    }
+}
+
+impl From<rusqlite::Error> for PersistError {
+    fn from(err: rusqlite::Error) -> Self {
+        Self::Sql(err.to_string())
+    }
+}
+
+impl From<rusqlite_migration::Error> for PersistError {
+    fn from(err: rusqlite_migration::Error) -> Self {
+        Self::Migration(err.to_string())
     }
 }
 
 impl From<serde_json::Error> for PersistError {
     fn from(err: serde_json::Error) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
diff --git a/libs/sdk-core/src/persist/swap.rs b/libs/sdk-core/src/persist/swap.rs
index 687462588..e8034e448 100644
--- a/libs/sdk-core/src/persist/swap.rs
+++ b/libs/sdk-core/src/persist/swap.rs
@@ -6,7 +6,6 @@ use super::{
     error::PersistResult,
 };
 use crate::OpeningFeeParams;
-use anyhow::anyhow;
 use rusqlite::{named_params, OptionalExtension, Params, Row, Transaction, TransactionBehavior};
 
 #[derive(Debug, Clone)]
@@ -88,7 +87,7 @@ impl SqliteStorage {
             &tx,
             swap_info.bitcoin_address,
             swap_info.channel_opening_fees.ok_or_else(|| {
-                PersistError::Generic(anyhow!("Dynamic fees must be set when creating a new swap"))
+                PersistError::generic("Dynamic fees must be set when creating a new swap")
             })?,
         )?;
 
diff --git a/libs/sdk-core/src/persist/transactions.rs b/libs/sdk-core/src/persist/transactions.rs
index 9c847ab27..15f60b150 100644
--- a/libs/sdk-core/src/persist/transactions.rs
+++ b/libs/sdk-core/src/persist/transactions.rs
@@ -2,7 +2,6 @@ use super::db::SqliteStorage;
 use super::error::{PersistError, PersistResult};
 use crate::lnurl::pay::model::SuccessActionProcessed;
 use crate::{ensure_sdk, models::*};
-use anyhow::anyhow;
 use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
 use rusqlite::Row;
 use rusqlite::{named_params, params, OptionalExtension};
@@ -115,7 +114,7 @@ impl SqliteStorage {
     ) -> PersistResult<()> {
         ensure_sdk!(
             new_metadata.len() <= METADATA_MAX_LEN,
-            PersistError::Generic(anyhow!(
+            PersistError::Generic(format!(
                 "Max metadata size ({} characters) has been exceeded",
                 METADATA_MAX_LEN
             ))
@@ -130,7 +129,7 @@ impl SqliteStorage {
             .exists(params![payment_hash])?;
 
         if !payment_exists {
-            return Err(PersistError::Generic(anyhow!("Payment not found")));
+            return Err(PersistError::generic("Payment not found"));
         }
 
         self.get_connection()?.execute(
diff --git a/libs/sdk-core/src/swap_in/error.rs b/libs/sdk-core/src/swap_in/error.rs
index 883a83b43..fa907385b 100644
--- a/libs/sdk-core/src/swap_in/error.rs
+++ b/libs/sdk-core/src/swap_in/error.rs
@@ -1,40 +1,49 @@
-use anyhow::anyhow;
-
 use crate::{error::SdkError, persist::error::PersistError};
 
 pub type SwapResult<T, E = SwapError> = Result<T, E>;
 
 #[derive(Debug, thiserror::Error)]
 pub enum SwapError {
-    #[error("Generic: {0}")]
-    Generic(#[from] anyhow::Error),
+    #[error("{0}")]
+    Generic(String),
 
     #[error(transparent)]
     Persistance(#[from] PersistError),
 
-    #[error("Service connectivity: {0}")]
-    ServiceConnectivity(anyhow::Error),
+    #[error("{0}")]
+    ServiceConnectivity(String),
 
-    #[error("Unsupported swap limits: {0}")]
-    UnsupportedSwapLimits(anyhow::Error),
+    #[error("{0}")]
+    UnsupportedSwapLimits(String),
 }
+
 impl SwapError {
+    pub(crate) fn generic(err: &str) -> Self {
+        Self::Generic(err.to_string())
+    }
+
     pub(crate) fn unsupported_swap_limits(err: &str) -> Self {
-        Self::UnsupportedSwapLimits(anyhow!(err.to_string()))
+        Self::UnsupportedSwapLimits(err.to_string())
+    }
+}
+
+impl From<anyhow::Error> for SwapError {
+    fn from(err: anyhow::Error) -> Self {
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<SdkError> for SwapError {
     fn from(value: SdkError) -> Self {
         match value {
-            SdkError::Generic { err } => Self::Generic(anyhow!(err)),
-            SdkError::ServiceConnectivity { err } => Self::ServiceConnectivity(anyhow!(err)),
+            SdkError::Generic { err } => Self::Generic(err),
+            SdkError::ServiceConnectivity { err } => Self::ServiceConnectivity(err),
         }
     }
 }
 
 impl From<tonic::Status> for SwapError {
-    fn from(err: tonic::Status) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+    fn from(status: tonic::Status) -> Self {
+        Self::Generic(status.to_string())
     }
 }
diff --git a/libs/sdk-core/src/swap_in/swap.rs b/libs/sdk-core/src/swap_in/swap.rs
index b551d54dd..8869e6ca2 100644
--- a/libs/sdk-core/src/swap_in/swap.rs
+++ b/libs/sdk-core/src/swap_in/swap.rs
@@ -179,7 +179,7 @@ impl BTCReceiveSwap {
         let node_state = self
             .persister
             .get_node_state()?
-            .ok_or(anyhow!("Node info not found"))?;
+            .ok_or(SwapError::generic("Node info not found"))?;
 
         // Calculate max_allowed_deposit based on absolute max and current node state
         let fn_max_allowed_deposit = |max_allowed_deposit_abs: i64| {
@@ -239,7 +239,7 @@ impl BTCReceiveSwap {
 
         // Ensure our address generation match the service
         if address_str != swap_reply.bitcoin_address {
-            return Err(SwapError::Generic(anyhow!("Wrong address: {address_str}")));
+            return Err(SwapError::Generic(format!("Wrong address: {address_str}")));
         }
 
         let swap_info = SwapInfo {
diff --git a/libs/sdk-core/src/swap_out/boltzswap.rs b/libs/sdk-core/src/swap_out/boltzswap.rs
index e9f9a8061..8685b0ec1 100644
--- a/libs/sdk-core/src/swap_out/boltzswap.rs
+++ b/libs/sdk-core/src/swap_out/boltzswap.rs
@@ -3,7 +3,6 @@ use std::collections::HashMap;
 use serde::{Deserialize, Serialize};
 use serde_json::to_string_pretty;
 
-use anyhow::{anyhow, Result};
 use const_format::concatcp;
 use reqwest::header::CONTENT_TYPE;
 use reqwest::Body;
@@ -223,7 +222,7 @@ pub struct BoltzApi {}
 #[tonic::async_trait]
 impl ReverseSwapServiceAPI for BoltzApi {
     async fn fetch_reverse_swap_fees(&self) -> ReverseSwapResult<ReverseSwapPairInfo> {
-        Ok(reverse_swap_pair_info().await?)
+        reverse_swap_pair_info().await
     }
 
     /// Call Boltz API and parse response as per https://docs.boltz.exchange/en/latest/api/#creating-reverse-swaps
@@ -254,18 +253,19 @@ impl ReverseSwapServiceAPI for BoltzApi {
                 routing_node,
             ))
             .send()
-            .await?
+            .await
+            .map_err(|e| ReverseSwapError::ServiceConnectivity(e.to_string()))?
             .text()
             .await
             .map_err(|e| {
-                ReverseSwapError::ServiceConnectivity(anyhow!(
+                ReverseSwapError::ServiceConnectivity(format!(
                     "(Boltz {CREATE_REVERSE_SWAP_ENDPOINT}) Failed to request creation of reverse swap: {e}"
                 ))
             })
             .and_then(|res| {
                 trace!("Boltz API create raw response {}", to_string_pretty(&res)?);
                 serde_json::from_str::<BoltzApiCreateReverseSwapResponse>(&res).map_err(|e| {
-                    ReverseSwapError::ServiceConnectivity(anyhow!(
+                    ReverseSwapError::ServiceConnectivity(format!(
                         "(Boltz {CREATE_REVERSE_SWAP_ENDPOINT}) Failed to parse create swap response: {e}"
                     ))
                 })
@@ -287,18 +287,19 @@ impl ReverseSwapServiceAPI for BoltzApi {
             .header(CONTENT_TYPE, "application/json")
             .body(Body::from(json!({ "id": id }).to_string()))
             .send()
-            .await?
+            .await
+            .map_err(|e| ReverseSwapError::ServiceConnectivity(e.to_string()))?
             .text()
             .await
             .map_err(|e| {
-                ReverseSwapError::ServiceConnectivity(anyhow!(
+                ReverseSwapError::ServiceConnectivity(format!(
                     "(Boltz {GET_SWAP_STATUS_ENDPOINT}) Failed to request swap status: {e}"
                 ))
             })
             .and_then(|res| {
                 trace!("Boltz API status raw response {}", to_string_pretty(&res)?);
                 serde_json::from_str::<BoltzApiReverseSwapStatus>(&res).map_err(|e| {
-                    ReverseSwapError::ServiceConnectivity(anyhow!(
+                    ReverseSwapError::ServiceConnectivity(format!(
                         "(Boltz {GET_SWAP_STATUS_ENDPOINT}) Failed to parse get status response: {e}"
                     ))
                 })
@@ -313,11 +314,12 @@ impl ReverseSwapServiceAPI for BoltzApi {
                 json!({ "routingNode": routing_node_id, "symbol": "BTC" }).to_string(),
             ))
             .send()
-            .await?
+            .await
+            .map_err(|e| ReverseSwapError::ServiceConnectivity(e.to_string()))?
             .text()
             .await
             .map_err(|e| {
-                ReverseSwapError::ServiceConnectivity(anyhow!(
+                ReverseSwapError::ServiceConnectivity(format!(
                     "(Boltz {GET_ROUTE_HINTS_ENDPOINT}) Failed to get routing hints: {e}"
                 ))
             })
@@ -328,7 +330,7 @@ impl ReverseSwapServiceAPI for BoltzApi {
                 );
                 serde_json::from_str::<BoltzRouteHints>(&res)
                 .map_err(|e| {
-                    ReverseSwapError::ServiceConnectivity(anyhow!(
+                    ReverseSwapError::ServiceConnectivity(format!(
                         "(Boltz {GET_ROUTE_HINTS_ENDPOINT}) Failed to parse get route hints response: {e}"
                     ))
                 })
@@ -337,10 +339,10 @@ impl ReverseSwapServiceAPI for BoltzApi {
     }
 }
 
-pub async fn reverse_swap_pair_info() -> Result<ReverseSwapPairInfo> {
+pub async fn reverse_swap_pair_info() -> ReverseSwapResult<ReverseSwapPairInfo> {
     let pairs: Pairs = get_parse_and_log_response(GET_PAIRS_ENDPOINT).await?;
     match pairs.pairs.get("BTC/BTC") {
-        None => Err(anyhow!("BTC pair not found")),
+        None => Err(ReverseSwapError::generic("BTC pair not found")),
         Some(btc_pair) => {
             debug!(
                 "Boltz API pair: {}",
diff --git a/libs/sdk-core/src/swap_out/error.rs b/libs/sdk-core/src/swap_out/error.rs
index c0c7463b8..45f36ec06 100644
--- a/libs/sdk-core/src/swap_out/error.rs
+++ b/libs/sdk-core/src/swap_out/error.rs
@@ -1,5 +1,3 @@
-use anyhow::anyhow;
-
 use crate::{
     bitcoin::{hashes, secp256k1},
     error::SdkError,
@@ -11,95 +9,110 @@ pub type ReverseSwapResult<T, E = ReverseSwapError> = Result<T, E>;
 
 #[derive(Debug, thiserror::Error)]
 pub enum ReverseSwapError {
-    #[error("Generic: {0}")]
-    Generic(#[from] anyhow::Error),
+    #[error("{0}")]
+    Generic(String),
 
     #[error("Claim tx feerate is too low")]
     ClaimFeerateTooLow,
 
-    #[error("Invalid destination address: {0}")]
-    InvalidDestinationAddress(anyhow::Error),
+    #[error("{0}")]
+    InvalidDestinationAddress(String),
 
     #[error(transparent)]
     Node(#[from] NodeError),
 
-    #[error("Service connectivity: {0}")]
-    ServiceConnectivity(anyhow::Error),
+    #[error("{0}")]
+    RouteNotFound(String),
+
+    #[error("{0}")]
+    ServiceConnectivity(String),
 
-    #[error("Unexpected invoice amount: {0}")]
-    UnexpectedInvoiceAmount(anyhow::Error),
+    #[error("{0}")]
+    UnexpectedInvoiceAmount(String),
 
     #[error("Unexpected lockup address")]
     UnexpectedLockupAddress,
 
-    #[error("Unexpected payment hash: {0}")]
-    UnexpectedPaymentHash(anyhow::Error),
+    #[error("{0}")]
+    UnexpectedPaymentHash(String),
 
     #[error("Unexpected redeem script")]
     UnexpectedRedeemScript,
-
-    #[error("Route not found: {0}")]
-    RouteNotFound(anyhow::Error),
 }
+
 impl ReverseSwapError {
     pub(crate) fn generic(err: &str) -> Self {
-        Self::Generic(anyhow!(err.to_string()))
+        Self::Generic(err.to_string())
+    }
+
+    pub(crate) fn unexpected_invoice_amount(err: &str) -> Self {
+        Self::UnexpectedInvoiceAmount(err.to_string())
+    }
+
+    pub(crate) fn unexpected_payment_hash(err: &str) -> Self {
+        Self::UnexpectedPaymentHash(err.to_string())
+    }
+}
+
+impl From<anyhow::Error> for ReverseSwapError {
+    fn from(err: anyhow::Error) -> Self {
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<hashes::hex::Error> for ReverseSwapError {
     fn from(err: hashes::hex::Error) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<hex::FromHexError> for ReverseSwapError {
     fn from(err: hex::FromHexError) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<crate::lightning_invoice::ParseOrSemanticError> for ReverseSwapError {
     fn from(err: crate::lightning_invoice::ParseOrSemanticError) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<PersistError> for ReverseSwapError {
     fn from(err: PersistError) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<reqwest::Error> for ReverseSwapError {
     fn from(err: reqwest::Error) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<SdkError> for ReverseSwapError {
     fn from(value: SdkError) -> Self {
         match value {
-            SdkError::Generic { err } => Self::Generic(anyhow!(err)),
-            SdkError::ServiceConnectivity { err } => Self::ServiceConnectivity(anyhow!(err)),
+            SdkError::Generic { err } => Self::Generic(err),
+            SdkError::ServiceConnectivity { err } => Self::ServiceConnectivity(err),
         }
     }
 }
 
 impl From<secp256k1::Error> for ReverseSwapError {
     fn from(err: secp256k1::Error) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
 
 impl From<serde_json::Error> for ReverseSwapError {
     fn from(err: serde_json::Error) -> Self {
-        Self::ServiceConnectivity(anyhow::Error::new(err))
+        Self::ServiceConnectivity(err.to_string())
     }
 }
 
 impl From<tonic::Status> for ReverseSwapError {
     fn from(err: tonic::Status) -> Self {
-        Self::Generic(anyhow::Error::new(err))
+        Self::Generic(err.to_string())
     }
 }
diff --git a/libs/sdk-core/src/swap_out/reverseswap.rs b/libs/sdk-core/src/swap_out/reverseswap.rs
index b09c6f4e7..57faaca4b 100644
--- a/libs/sdk-core/src/swap_out/reverseswap.rs
+++ b/libs/sdk-core/src/swap_out/reverseswap.rs
@@ -135,7 +135,7 @@ impl BTCSendSwap {
     fn validate_recipient_address(claim_pubkey: &str) -> ReverseSwapResult<()> {
         Address::from_str(claim_pubkey)
             .map(|_| ())
-            .map_err(|e| ReverseSwapError::InvalidDestinationAddress(anyhow::Error::new(e)))
+            .map_err(|e| ReverseSwapError::InvalidDestinationAddress(e.to_string()))
     }
 
     pub(crate) fn validate_claim_tx_fee(claim_fee: u64) -> ReverseSwapResult<()> {
@@ -159,14 +159,14 @@ impl BTCSendSwap {
         routing_hints
             .first()
             .ok_or_else(|| {
-                ReverseSwapError::RouteNotFound(anyhow!(
+                ReverseSwapError::RouteNotFound(format!(
                     "No route hints found for reverse routing node {reverse_routing_node:?}"
                 ))
             })?
             .hops
             .first()
             .ok_or_else(|| {
-                ReverseSwapError::RouteNotFound(anyhow!(
+                ReverseSwapError::RouteNotFound(format!(
                     "No hops found for reverse routing node {reverse_routing_node:?}"
                 ))
             })
@@ -239,17 +239,17 @@ impl BTCSendSwap {
                 // TODO It doesn't fail when trying to pay more sats than max_payable?
                 match pay_thread_res {
                     // Paying a HODL invoice does not typically return, so if send_payment() returned, it's an abnormal situation
-                    Ok(Ok(res)) => Err(NodeError::PaymentFailed(anyhow!("Payment of HODL invoice unexpectedly returned: {res:?}"))),
+                    Ok(Ok(res)) => Err(NodeError::PaymentFailed(format!("Payment of HODL invoice unexpectedly returned: {res:?}"))),
 
                     // send_payment() returned an error, so we know paying the HODL invoice failed
-                    Ok(Err(e)) => Err(NodeError::PaymentFailed(anyhow!("Failed to pay HODL invoice: {e}"))),
+                    Ok(Err(e)) => Err(NodeError::PaymentFailed(format!("Failed to pay HODL invoice: {e}"))),
 
                     // send_payment() has been trying to pay for longer than the payment timeout
-                    Err(e) => Err(NodeError::PaymentTimeout(anyhow!("Trying to pay the HODL invoice timed out: {e}")))
+                    Err(e) => Err(NodeError::PaymentTimeout(format!("Trying to pay the HODL invoice timed out: {e}")))
                 }
             },
             paid_invoice_res = self.poll_initial_boltz_status_transition(&created_rsi.id) => {
-                paid_invoice_res.map(|_| created_rsi.clone()).map_err(NodeError::Generic)
+                paid_invoice_res.map(|_| created_rsi.clone()).map_err(|e| NodeError::Generic(e.to_string()))
             }
         };
 
@@ -349,7 +349,7 @@ impl BTCSendSwap {
                 Ok(res)
             }
             BoltzApiCreateReverseSwapResponse::BoltzApiError { error } => {
-                Err(ReverseSwapError::ServiceConnectivity(anyhow!(
+                Err(ReverseSwapError::ServiceConnectivity(format!(
                     "(Boltz) Failed to create reverse swap: {error}"
                 )))
             }
diff --git a/tools/sdk-cli/Cargo.lock b/tools/sdk-cli/Cargo.lock
index 566f713cc..1031b513f 100644
--- a/tools/sdk-cli/Cargo.lock
+++ b/tools/sdk-cli/Cargo.lock
@@ -458,7 +458,7 @@ dependencies = [
 
 [[package]]
 name = "breez-sdk-core"
-version = "0.3.2"
+version = "0.4.0"
 dependencies = [
  "aes",
  "anyhow",


Test patch:
diff --git a/libs/sdk-core/src/test_utils.rs b/libs/sdk-core/src/test_utils.rs
index 6fc0ab97c..e2d8b1e1e 100644
--- a/libs/sdk-core/src/test_utils.rs
+++ b/libs/sdk-core/src/test_utils.rs
@@ -3,7 +3,7 @@ use std::pin::Pin;
 use std::time::{Duration, SystemTime};
 use std::{mem, vec};
 
-use anyhow::{anyhow, Error, Result};
+use anyhow::{Error, Result};
 use chrono::{SecondsFormat, Utc};
 use gl_client::signer::model::greenlight::amount::Unit;
 use gl_client::signer::model::greenlight::Amount;
@@ -178,15 +178,15 @@ impl ReverseSwapServiceAPI for MockReverseSwapperAPI {
         _pair_hash: String,
         _routing_node: String,
     ) -> ReverseSwapResult<BoltzApiCreateReverseSwapResponse> {
-        Err(ReverseSwapError::Generic(anyhow!("Not implemented")))
+        Err(ReverseSwapError::generic("Not implemented"))
     }
 
     async fn get_boltz_status(&self, _id: String) -> ReverseSwapResult<BoltzApiReverseSwapStatus> {
-        Err(ReverseSwapError::Generic(anyhow!("Not implemented")))
+        Err(ReverseSwapError::generic("Not implemented"))
     }
 
     async fn get_route_hints(&self, _routing_node_id: String) -> ReverseSwapResult<Vec<RouteHint>> {
-        Err(ReverseSwapError::Generic(anyhow!("Not implemented")))
+        Err(ReverseSwapError::generic("Not implemented"))
     }
 }
 
@@ -226,11 +226,11 @@ impl Default for MockChainService {
 
 #[tonic::async_trait]
 impl ChainService for MockChainService {
-    async fn recommended_fees(&self) -> Result<RecommendedFees> {
+    async fn recommended_fees(&self) -> SdkResult<RecommendedFees> {
         Ok(self.recommended_fees.clone())
     }
 
-    async fn address_transactions(&self, address: String) -> Result<Vec<OnchainTx>> {
+    async fn address_transactions(&self, address: String) -> SdkResult<Vec<OnchainTx>> {
         Ok(self
             .address_to_transactions
             .get(&address)
@@ -238,11 +238,11 @@ impl ChainService for MockChainService {
             .clone())
     }
 
-    async fn current_tip(&self) -> Result<u32> {
+    async fn current_tip(&self) -> SdkResult<u32> {
         Ok(self.tip)
     }
 
-    async fn transaction_outspends(&self, _txid: String) -> Result<Vec<Outspend>> {
+    async fn transaction_outspends(&self, _txid: String) -> SdkResult<Vec<Outspend>> {
         Ok(vec![Outspend {
             spent: true,
             txid: Some("test-tx-id".into()),
@@ -256,7 +256,7 @@ impl ChainService for MockChainService {
         }])
     }
 
-    async fn broadcast_transaction(&self, _tx: Vec<u8>) -> Result<String> {
+    async fn broadcast_transaction(&self, _tx: Vec<u8>) -> SdkResult<String> {
         let mut array = [0; 32];
         rand::thread_rng().fill(&mut array);
         Ok(hex::encode(array))
@@ -332,7 +332,7 @@ pub struct MockNodeAPI {
 #[tonic::async_trait]
 impl NodeAPI for MockNodeAPI {
     fn node_credentials(&self) -> NodeResult<Option<NodeCredentials>> {
-        Err(NodeError::Generic(anyhow!("Not implemented")))
+        Err(NodeError::Generic("Not implemented".to_string()))
     }
 
     async fn configure_node(&self, _close_to_address: Option<String>) -> NodeResult<()> {
@@ -369,7 +369,7 @@ impl NodeAPI for MockNodeAPI {
     }
 
     async fn send_pay(&self, _bolt11: String, _max_hops: u32) -> NodeResult<PaymentResponse> {
-        Err(NodeError::Generic(anyhow!("Not implemented")))
+        Err(NodeError::Generic("Not implemented".to_string()))
     }
 
     async fn send_payment(
@@ -409,7 +409,7 @@ impl NodeAPI for MockNodeAPI {
         &self,
         _req: PrepareRedeemOnchainFundsRequest,
     ) -> NodeResult<PrepareRedeemOnchainFundsResponse> {
-        Err(NodeError::Generic(anyhow!("Not implemented")))
+        Err(NodeError::Generic("Not implemented".to_string()))
     }
 
     async fn start_signer(&self, _shutdown: mpsc::Receiver<()>) {}
@@ -445,13 +445,13 @@ impl NodeAPI for MockNodeAPI {
     async fn stream_incoming_payments(
         &self,
     ) -> NodeResult<Streaming<gl_client::signer::model::greenlight::IncomingPayment>> {
-        Err(NodeError::Generic(anyhow!("Not implemented")))
+        Err(NodeError::Generic("Not implemented".to_string()))
     }
 
     async fn stream_log_messages(
         &self,
     ) -> NodeResult<Streaming<gl_client::signer::model::greenlight::LogEntry>> {
-        Err(NodeError::Generic(anyhow!("Not implemented")))
+        Err(NodeError::Generic("Not implemented".to_string()))
     }
 
     async fn static_backup(&self) -> NodeResult<Vec<String>> {
@@ -459,7 +459,7 @@ impl NodeAPI for MockNodeAPI {
     }
 
     async fn execute_command(&self, _command: String) -> NodeResult<String> {
-        Err(NodeError::Generic(anyhow!("Not implemented")))
+        Err(NodeError::Generic("Not implemented".to_string()))
     }
 
     async fn generate_diagnostic_data(&self) -> NodeResult<String> {
@@ -472,7 +472,7 @@ impl NodeAPI for MockNodeAPI {
         _max_hops: u32,
         _last_hop: Option<&RouteHintHop>,
     ) -> NodeResult<Vec<MaxChannelAmount>> {
-        Err(NodeError::Generic(anyhow!("Not implemented")))
+        Err(NodeError::Generic("Not implemented".to_string()))
     }
 
     fn derive_bip32_key(&self, _path: Vec<ChildNumber>) -> NodeResult<ExtendedPrivKey> {
@@ -533,7 +533,7 @@ impl MockNodeAPI {
     ) -> NodeResult<Payment> {
         let inv = bolt11
             .parse::<crate::lightning_invoice::Bolt11Invoice>()
-            .map_err(|e| NodeError::Generic(anyhow::Error::new(e)))?;
+            .map_err(|e| NodeError::Generic(e.to_string()))?;
 
         self.add_dummy_payment(inv, preimage, status).await
     }
@@ -729,8 +729,7 @@ pub(crate) fn rand_invoice_with_description_hash_and_preimage(
         .description_hash(expected_desc_hash)
         .amount_milli_satoshis(50 * 1000)
         .payment_hash(
-            Hash::from_slice(payment_hash)
-                .map_err(|e| InvoiceError::Generic(anyhow::Error::new(e)))?,
+            Hash::from_slice(payment_hash).map_err(|e| InvoiceError::Generic(e.to_string()))?,
         )
         .payment_secret(payment_secret)
         .current_timestamp()