Sfoglia il codice sorgente

refactor: replace reqwest with bitreq HTTP client

Migrate the codebase from reqwest to bitreq for HTTP requests. This
affects the wallet transport layer, OIDC client, CLI commands, and
integration tests.

- Replace reqwest::Client with bitreq::Client across all crates
- Update HTTP request/response handling to use bitreq API
- Remove reqwest and serde_urlencoded from workspace dependencies
- Use url::form_urlencoded for URL encoding instead of serde_urlencoded
- Adapt proxy configuration to bitreq's proxy API

TODO:
- [ ] Do something with accept_invalid_certs
Cesar Rodas 1 settimana fa
parent
commit
0733ae3068

+ 36 - 19
Cargo.lock

@@ -953,6 +953,22 @@ dependencies = [
 ]
 
 [[package]]
+name = "bitreq"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bf88316bfce42d7db313826acfa4325857c085e26f163c3e8c1cfb38fef4007"
+dependencies = [
+ "base64 0.22.1",
+ "rustls 0.21.12",
+ "rustls-webpki 0.101.7",
+ "serde",
+ "serde_json",
+ "tokio",
+ "tokio-rustls 0.24.1",
+ "webpki-roots 0.25.4",
+]
+
+[[package]]
 name = "bitvec"
 version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1160,9 +1176,9 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.2.53"
+version = "1.2.54"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932"
+checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
 dependencies = [
  "find-msvc-tools",
  "jobserver",
@@ -1181,6 +1197,7 @@ dependencies = [
  "async-trait",
  "bip39",
  "bitcoin 0.32.8",
+ "bitreq",
  "cbor-diag",
  "cdk-common",
  "cdk-fake-wallet",
@@ -1202,7 +1219,6 @@ dependencies = [
  "nostr-sdk",
  "rand 0.9.2",
  "regex",
- "reqwest",
  "ring 0.17.14",
  "rustls 0.23.36",
  "serde",
@@ -1254,6 +1270,7 @@ dependencies = [
  "anyhow",
  "bip39",
  "bitcoin 0.32.8",
+ "bitreq",
  "cdk",
  "cdk-common",
  "cdk-redb",
@@ -1262,7 +1279,6 @@ dependencies = [
  "home",
  "lightning 0.2.0",
  "nostr-sdk",
- "reqwest",
  "serde",
  "serde_json",
  "serde_with",
@@ -1330,11 +1346,11 @@ version = "0.14.0"
 dependencies = [
  "async-trait",
  "bitcoin 0.32.8",
+ "bitreq",
  "cdk-common",
  "futures",
  "lightning 0.2.0",
  "lightning-invoice 0.34.0",
- "reqwest",
  "serde",
  "serde_json",
  "thiserror 2.0.18",
@@ -1381,6 +1397,7 @@ dependencies = [
  "axum 0.8.8",
  "bip39",
  "bitcoin 0.32.8",
+ "bitreq",
  "cashu",
  "cdk",
  "cdk-axum",
@@ -1401,7 +1418,6 @@ dependencies = [
  "ln-regtest-rs",
  "once_cell",
  "rand 0.9.2",
- "reqwest",
  "serde",
  "serde_json",
  "tokio",
@@ -1411,6 +1427,7 @@ dependencies = [
  "tower-service",
  "tracing",
  "tracing-subscriber",
+ "url",
  "uuid",
  "web-time",
 ]
@@ -1428,7 +1445,6 @@ dependencies = [
  "rust-embed",
  "serde",
  "serde_json",
- "serde_urlencoded",
  "thiserror 2.0.18",
  "tokio",
  "tokio-stream",
@@ -1436,6 +1452,7 @@ dependencies = [
  "tower 0.5.3",
  "tower-http",
  "tracing",
+ "url",
  "urlencoding",
 ]
 
@@ -1549,13 +1566,13 @@ version = "0.14.0"
 dependencies = [
  "async-trait",
  "base64 0.22.1",
+ "bitreq",
  "cashu",
  "cdk",
  "cdk-common",
  "cdk-sqlite",
  "chrono",
  "nostr-sdk",
- "reqwest",
  "rustls 0.23.36",
  "serde",
  "serde_json",
@@ -1631,10 +1648,10 @@ name = "cdk-prometheus"
 version = "0.14.0"
 dependencies = [
  "anyhow",
+ "bitreq",
  "futures",
  "once_cell",
  "prometheus",
- "reqwest",
  "serde",
  "serde_json",
  "sysinfo",
@@ -3618,7 +3635,7 @@ dependencies = [
  "libc",
  "percent-encoding",
  "pin-project-lite",
- "socket2 0.6.1",
+ "socket2 0.6.2",
  "tokio",
  "tower-service",
  "tracing",
@@ -5636,7 +5653,7 @@ dependencies = [
  "quinn-udp",
  "rustc-hash",
  "rustls 0.23.36",
- "socket2 0.6.1",
+ "socket2 0.6.2",
  "thiserror 2.0.18",
  "tokio",
  "tracing",
@@ -5673,16 +5690,16 @@ dependencies = [
  "cfg_aliases",
  "libc",
  "once_cell",
- "socket2 0.6.1",
+ "socket2 0.6.2",
  "tracing",
  "windows-sys 0.60.2",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.43"
+version = "1.0.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
+checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
 dependencies = [
  "proc-macro2",
 ]
@@ -6763,9 +6780,9 @@ dependencies = [
 
 [[package]]
 name = "socket2"
-version = "0.6.1"
+version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
+checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
 dependencies = [
  "libc",
  "windows-sys 0.60.2",
@@ -7245,7 +7262,7 @@ dependencies = [
  "parking_lot",
  "pin-project-lite",
  "signal-hook-registry",
- "socket2 0.6.1",
+ "socket2 0.6.2",
  "tokio-macros",
  "windows-sys 0.61.2",
 ]
@@ -7301,7 +7318,7 @@ dependencies = [
  "postgres-protocol",
  "postgres-types",
  "rand 0.9.2",
- "socket2 0.6.1",
+ "socket2 0.6.2",
  "tokio",
  "tokio-util",
  "whoami",
@@ -7547,7 +7564,7 @@ dependencies = [
  "hyper-util",
  "percent-encoding",
  "pin-project",
- "socket2 0.6.1",
+ "socket2 0.6.2",
  "sync_wrapper 1.0.2",
  "tokio",
  "tokio-rustls 0.26.4",

+ 1 - 10
Cargo.toml

@@ -94,16 +94,7 @@ utoipa = { version = "5.3.1", features = [
     "preserve_path_order",
 ]}
 serde_with = "3"
-reqwest = { version = "0.12", default-features = false, features = [
-    "json",
-    "rustls-tls",
-    "rustls-tls-native-roots",
-    "socks",
-    "zstd",
-    "brotli",
-    "gzip",
-    "deflate",
-]}
+bitreq = { version = "0.3.1", default-features = false, features = ["https-rustls", "async-https-rustls", "std", "proxy", "json-using-serde"] }
 once_cell = "1.20.2"
 web-time = "1.1.0"
 rand = "0.9.1"

+ 1 - 1
crates/cdk-cli/Cargo.toml

@@ -34,7 +34,7 @@ tracing.workspace = true
 tracing-subscriber.workspace = true
 home.workspace = true
 nostr-sdk = { workspace = true }
-reqwest.workspace = true
+bitreq.workspace = true
 url.workspace = true
 serde_with.workspace = true
 lightning.workspace = true

+ 16 - 20
crates/cdk-cli/src/sub_commands/cat_device_login.rs

@@ -82,20 +82,18 @@ async fn get_device_code_token(mint_info: &MintInfo) -> (String, String) {
     let device_auth_url = oidc_config.device_authorization_endpoint;
 
     // Make the device code request
-    let client = reqwest::Client::new();
-    let device_code_response = client
-        .post(device_auth_url)
-        .form(&[
-            ("client_id", client_id.clone().as_str()),
-            ("scope", "openid offline_access"),
-        ])
-        .send()
+    let params: String = url::form_urlencoded::Serializer::new(String::new())
+        .append_pair("client_id", &client_id)
+        .append_pair("scope", "openid offline_access")
+        .finish();
+    let device_code_response = bitreq::post(device_auth_url)
+        .with_body(params)
+        .send_async()
         .await
         .expect("Failed to send device code request");
 
     let device_code_data: serde_json::Value = device_code_response
         .json()
-        .await
         .expect("Failed to parse device code response");
 
     let device_code = device_code_data["device_code"]
@@ -129,21 +127,20 @@ async fn get_device_code_token(mint_info: &MintInfo) -> (String, String) {
     loop {
         sleep(Duration::from_secs(interval)).await;
 
-        let token_response = client
-            .post(&token_url)
-            .form(&[
-                ("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
-                ("device_code", device_code),
-                ("client_id", client_id.clone().as_str()),
-            ])
-            .send()
+        let params: String = url::form_urlencoded::Serializer::new(String::new())
+            .append_pair("grant_type", "urn:ietf:params:oauth:grant-type:device_code")
+            .append_pair("device_code", device_code)
+            .append_pair("client_id", &client_id)
+            .finish();
+        let token_response = bitreq::post(&token_url)
+            .with_body(params)
+            .send_async()
             .await
             .expect("Failed to send token request");
 
-        if token_response.status().is_success() {
+        if token_response.status_code == 200 {
             let token_data: serde_json::Value = token_response
                 .json()
-                .await
                 .expect("Failed to parse token response");
 
             let access_token = token_data["access_token"]
@@ -160,7 +157,6 @@ async fn get_device_code_token(mint_info: &MintInfo) -> (String, String) {
         } else {
             let error_data: serde_json::Value = token_response
                 .json()
-                .await
                 .expect("Failed to parse error response");
 
             let error = error_data["error"].as_str().unwrap_or("unknown_error");

+ 12 - 18
crates/cdk-cli/src/sub_commands/cat_login.rs

@@ -85,28 +85,22 @@ async fn get_access_token(mint_info: &MintInfo, user: &str, password: &str) -> (
         .expect("Failed to get OIDC config")
         .token_endpoint;
 
-    // Create the request parameters
-    let params = [
-        ("grant_type", "password"),
-        ("client_id", &client_id),
-        ("scope", "openid offline_access"),
-        ("username", user),
-        ("password", password),
-    ];
-
     // Make the token request directly
-    let client = reqwest::Client::new();
-    let response = client
-        .post(token_url)
-        .form(&params)
-        .send()
+    let params: String = url::form_urlencoded::Serializer::new(String::new())
+        .append_pair("grant_type", "password")
+        .append_pair("client_id", &client_id)
+        .append_pair("scope", "openid offline_access")
+        .append_pair("username", user)
+        .append_pair("password", password)
+        .finish();
+    let response = bitreq::post(token_url)
+        .with_body(params)
+        .send_async()
         .await
         .expect("Failed to send token request");
 
-    let token_response: serde_json::Value = response
-        .json()
-        .await
-        .expect("Failed to parse token response");
+    let token_response: serde_json::Value =
+        response.json().expect("Failed to parse token response");
 
     let access_token = token_response["access_token"]
         .as_str()

+ 12 - 12
crates/cdk-cli/src/sub_commands/mint_blind_auth.rs

@@ -163,25 +163,25 @@ async fn refresh_access_token(
     // Get the token endpoint from the OIDC configuration
     let token_url = oidc_client.get_oidc_config().await?.token_endpoint;
 
-    // Create the request parameters for token refresh
-    let params = [
-        ("grant_type", "refresh_token"),
-        ("refresh_token", refresh_token),
-        ("client_id", "cashu-client"), // Using default client ID
-    ];
-
     // Make the token refresh request
-    let client = reqwest::Client::new();
-    let response = client.post(token_url).form(&params).send().await?;
+    let params: String = url::form_urlencoded::Serializer::new(String::new())
+        .append_pair("grant_type", "refresh_token")
+        .append_pair("refresh_token", refresh_token)
+        .append_pair("client_id", "cashu-client")
+        .finish();
+    let response = bitreq::post(token_url)
+        .with_body(params)
+        .send_async()
+        .await?;
 
-    if !response.status().is_success() {
+    if response.status_code != 200 {
         return Err(anyhow::anyhow!(
             "Token refresh failed with status: {}",
-            response.status()
+            response.status_code
         ));
     }
 
-    let token_response: serde_json::Value = response.json().await?;
+    let token_response: serde_json::Value = response.json()?;
 
     let access_token = token_response["access_token"]
         .as_str()

+ 6 - 0
crates/cdk-common/src/error.rs

@@ -461,6 +461,12 @@ impl ErrorResponse {
     }
 
     /// Error response from json
+    pub fn from_slice(json: &[u8]) -> Result<Self, serde_json::Error> {
+        let value: Value = serde_json::from_slice(json)?;
+        Self::from_value(value)
+    }
+
+    /// Error response from json
     pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
         let value: Value = serde_json::from_str(json)?;
 

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

@@ -24,7 +24,7 @@ serde_json.workspace = true
 lightning-invoice.workspace = true
 lightning.workspace = true
 tokio-stream.workspace = true
-reqwest.workspace = true
+bitreq.workspace = true
 uuid.workspace = true
 
 [lints]

+ 2 - 2
crates/cdk-fake-wallet/src/lib.rs

@@ -105,11 +105,11 @@ impl ExchangeRateCache {
     /// Fetch fresh rate and update cache
     async fn fetch_fresh_rate(&self, currency: &CurrencyUnit) -> Result<f64, Error> {
         let url = "https://mempool.space/api/v1/prices";
-        let response = reqwest::get(url)
+        let response = bitreq::get(url)
+            .send_async()
             .await
             .map_err(|_| Error::UnknownInvoiceAmount)?
             .json::<MempoolPricesResponse>()
-            .await
             .map_err(|_| Error::UnknownInvoiceAmount)?;
 
         let rate = Self::rate_for_currency(&response, currency)?;

+ 2 - 1
crates/cdk-integration-tests/Cargo.toml

@@ -47,7 +47,8 @@ tokio-tungstenite.workspace = true
 tower-http = { workspace = true, features = ["cors"] }
 tower-service = "0.3.3"
 tokio-util.workspace = true
-reqwest.workspace = true
+bitreq.workspace = true
+url.workspace = true
 bitcoin = "0.32.0"
 clap = { workspace = true, features = ["derive"] }
 web-time.workspace = true

+ 3 - 3
crates/cdk-integration-tests/src/shared.rs

@@ -50,17 +50,17 @@ pub async fn wait_for_mint_ready_with_shutdown(
 
         tokio::select! {
             // Try to make a request to the mint info endpoint
-            result = reqwest::get(&url) => {
+            result = bitreq::get(&url).send_async() => {
                 match result {
                     Ok(response) => {
-                        if response.status().is_success() {
+                        if response.status_code == 200 {
                             println!("Mint on port {port} is ready");
                             return Ok(());
                         } else {
                             println!(
                                 "Mint on port {} returned status: {}",
                                 port,
-                                response.status()
+                                response.status_code
                             );
                         }
                     }

+ 23 - 34
crates/cdk-integration-tests/tests/fake_auth.rs

@@ -764,28 +764,22 @@ async fn get_access_token(mint_info: &MintInfo) -> (String, String) {
         .expect("Failed to get OIDC config")
         .token_endpoint;
 
-    // Create the request parameters
-    let (user, password) = get_oidc_credentials();
-    let params = [
-        ("grant_type", "password"),
-        ("client_id", "cashu-client"),
-        ("username", &user),
-        ("password", &password),
-    ];
-
     // Make the token request directly
-    let client = reqwest::Client::new();
-    let response = client
-        .post(token_url)
-        .form(&params)
-        .send()
+    let (user, password) = get_oidc_credentials();
+    let params: String = url::form_urlencoded::Serializer::new(String::new())
+        .append_pair("grant_type", "password")
+        .append_pair("client_id", "cashu-client")
+        .append_pair("username", &user)
+        .append_pair("password", &password)
+        .finish();
+    let response = bitreq::post(token_url)
+        .with_body(params)
+        .send_async()
         .await
         .expect("Failed to send token request");
 
-    let token_response: serde_json::Value = response
-        .json()
-        .await
-        .expect("Failed to parse token response");
+    let token_response: serde_json::Value =
+        response.json().expect("Failed to parse token response");
 
     let access_token = token_response["access_token"]
         .as_str()
@@ -822,33 +816,28 @@ async fn get_custom_access_token(
         .map_err(|_| Error::Custom("Failed to get OIDC config".to_string()))?
         .token_endpoint;
 
-    // Create the request parameters
-    let params = [
-        ("grant_type", "password"),
-        ("client_id", "cashu-client"),
-        ("username", username),
-        ("password", password),
-    ];
-
     // Make the token request directly
-    let client = reqwest::Client::new();
-    let response = client
-        .post(token_url)
-        .form(&params)
-        .send()
+    let params: String = url::form_urlencoded::Serializer::new(String::new())
+        .append_pair("grant_type", "password")
+        .append_pair("client_id", "cashu-client")
+        .append_pair("username", username)
+        .append_pair("password", password)
+        .finish();
+    let response = bitreq::post(token_url)
+        .with_body(params)
+        .send_async()
         .await
         .map_err(|_| Error::Custom("Failed to send token request".to_string()))?;
 
-    if !response.status().is_success() {
+    if response.status_code != 200 {
         return Err(Error::Custom(format!(
             "Token request failed with status: {}",
-            response.status()
+            response.status_code
         )));
     }
 
     let token_response: serde_json::Value = response
         .json()
-        .await
         .map_err(|_| Error::Custom("Failed to parse token response".to_string()))?;
 
     let access_token = token_response["access_token"]

+ 11 - 16
crates/cdk-integration-tests/tests/ldk_node.rs

@@ -6,17 +6,16 @@ async fn test_ldk_node_mint_info() -> Result<()> {
     // This test just verifies that the LDK-Node mint is running and responding
     let mint_url = get_mint_url_from_env();
 
-    // Create an HTTP client
-    let client = reqwest::Client::new();
-
     // Make a request to the info endpoint
-    let response = client.get(format!("{}/v1/info", mint_url)).send().await?;
+    let response = bitreq::get(format!("{}/v1/info", mint_url))
+        .send_async()
+        .await?;
 
     // Check that we got a successful response
-    assert_eq!(response.status(), 200);
+    assert_eq!(response.status_code, 200);
 
     // Try to parse the response as JSON
-    let info: serde_json::Value = response.json().await?;
+    let info: serde_json::Value = response.json()?;
 
     // Verify that we got some basic fields
     assert!(info.get("name").is_some());
@@ -33,9 +32,6 @@ async fn test_ldk_node_mint_quote() -> Result<()> {
     // This test verifies that we can create a mint quote with the LDK-Node mint
     let mint_url = get_mint_url_from_env();
 
-    // Create an HTTP client
-    let client = reqwest::Client::new();
-
     // Create a mint quote request
     let quote_request = serde_json::json!({
         "amount": 1000,
@@ -43,21 +39,20 @@ async fn test_ldk_node_mint_quote() -> Result<()> {
     });
 
     // Make a request to create a mint quote
-    let response = client
-        .post(format!("{}/v1/mint/quote/bolt11", mint_url))
-        .json(&quote_request)
-        .send()
+    let response = bitreq::post(format!("{}/v1/mint/quote/bolt11", mint_url))
+        .with_json(&quote_request)?
+        .send_async()
         .await?;
 
     // Print the response for debugging
-    let status = response.status();
-    let text = response.text().await?;
+    let status = response.status_code;
+    let text = response.as_str().unwrap_or_default();
     println!("Mint quote response status: {}", status);
     println!("Mint quote response body: {}", text);
 
     // For now, we'll just check that we get a response (even if it's an error)
     // In a real test, we'd want to verify the quote was created correctly
-    assert!(status.is_success() || status.as_u16() < 500);
+    assert!(status == 200 || status < 500);
 
     Ok(())
 }

+ 16 - 37
crates/cdk-integration-tests/tests/nutshell_wallet.rs

@@ -1,7 +1,6 @@
 use std::time::Duration;
 
 use cdk_fake_wallet::create_fake_invoice;
-use reqwest::Client;
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
 use tokio::time::sleep;
@@ -22,18 +21,14 @@ const DEFAULT_TEST_AMOUNT: u64 = 10000;
 
 /// Helper function to mint tokens via Lightning invoice
 async fn mint_tokens(base_url: &str, amount: u64) -> String {
-    let client = Client::new();
-
     // Create an invoice for the specified amount
     let invoice_url = format!("{}/lightning/create_invoice?amount={}", base_url, amount);
 
-    let invoice_response = client
-        .post(&invoice_url)
-        .send()
+    let invoice_response: InvoiceResponse = bitreq::post(&invoice_url)
+        .send_async()
         .await
         .expect("Failed to send invoice creation request")
-        .json::<InvoiceResponse>()
-        .await
+        .json()
         .expect("Failed to parse invoice response");
 
     println!("Created invoice: {}", invoice_response.payment_request);
@@ -43,7 +38,6 @@ async fn mint_tokens(base_url: &str, amount: u64) -> String {
 
 /// Helper function to wait for payment confirmation
 async fn wait_for_payment_confirmation(base_url: &str, payment_request: &str) {
-    let client = Client::new();
     let check_url = format!(
         "{}/lightning/invoice_state?payment_request={}",
         base_url, payment_request
@@ -57,16 +51,14 @@ async fn wait_for_payment_confirmation(base_url: &str, payment_request: &str) {
             attempt, MAX_PAYMENT_CHECK_ATTEMPTS
         );
 
-        let response = client
-            .get(&check_url)
-            .send()
+        let response = bitreq::get(&check_url)
+            .send_async()
             .await
             .expect("Failed to send payment check request");
 
-        if response.status().is_success() {
+        if response.status_code == 200 {
             let state: Value = response
                 .json()
-                .await
                 .expect("Failed to parse payment state response");
             println!("Payment state: {:?}", state);
 
@@ -77,7 +69,7 @@ async fn wait_for_payment_confirmation(base_url: &str, payment_request: &str) {
                 }
             }
         } else {
-            println!("Failed to check payment state: {}", response.status());
+            println!("Failed to check payment state: {}", response.status_code);
         }
 
         sleep(Duration::from_millis(PAYMENT_CHECK_DELAY_MS)).await;
@@ -90,18 +82,13 @@ async fn wait_for_payment_confirmation(base_url: &str, payment_request: &str) {
 
 /// Helper function to get the current wallet balance
 async fn get_wallet_balance(base_url: &str) -> u64 {
-    let client = Client::new();
     let balance_url = format!("{}/balance", base_url);
 
-    let balance_response = client
-        .get(&balance_url)
-        .send()
+    let balance: Value = bitreq::get(&balance_url)
+        .send_async()
         .await
-        .expect("Failed to send balance request");
-
-    let balance: Value = balance_response
+        .expect("Failed to send balance request")
         .json()
-        .await
         .expect("Failed to parse balance response");
 
     balance["balance"]
@@ -149,15 +136,12 @@ async fn test_nutshell_wallet_swap() {
 
     let send_amount = 100;
     let send_url = format!("{}/send?amount={}", base_url, send_amount);
-    let client = Client::new();
 
-    let response: Value = client
-        .post(&send_url)
-        .send()
+    let response: Value = bitreq::post(&send_url)
+        .send_async()
         .await
         .expect("Failed to send payment check request")
         .json()
-        .await
         .expect("Valid json");
 
     // Extract the token and remove the surrounding quotes
@@ -170,13 +154,11 @@ async fn test_nutshell_wallet_swap() {
 
     let receive_url = format!("{}/receive?token={}", base_url, token);
 
-    let response: Value = client
-        .post(&receive_url)
-        .send()
+    let response: Value = bitreq::post(&receive_url)
+        .send_async()
         .await
         .expect("Failed to receive request")
         .json()
-        .await
         .expect("Valid json");
 
     let balance = response
@@ -217,16 +199,13 @@ async fn test_nutshell_wallet_melt() {
     let payment_amount = 1000; // 1000 sats
     let fake_invoice = create_fake_invoice(payment_amount, "Test payment".to_string());
     let pay_url = format!("{}/lightning/pay_invoice?bolt11={}", base_url, fake_invoice);
-    let client = Client::new();
 
     // Step 4: Pay the invoice
-    let _response: Value = client
-        .post(&pay_url)
-        .send()
+    let _response: Value = bitreq::post(&pay_url)
+        .send_async()
         .await
         .expect("Failed to send pay request")
         .json()
-        .await
         .expect("Failed to parse pay response");
 
     let final_balance = get_wallet_balance(&base_url).await;

+ 1 - 1
crates/cdk-ldk-node/Cargo.toml

@@ -23,11 +23,11 @@ ldk-node.workspace = true
 tokio-stream = { workspace = true, features = ["sync"] }
 serde.workspace = true
 serde_json.workspace = true
+url.workspace = true
 maud = "0.27.0"
 tower.workspace = true
 tower-http.workspace = true
 rust-embed = "8.5.0"
-serde_urlencoded = "0.7"
 urlencoding = "2.1"
 
 [lints]

+ 7 - 2
crates/cdk-ldk-node/src/web/handlers/onchain.rs

@@ -227,8 +227,13 @@ pub async fn post_send_onchain(
     State(_state): State<AppState>,
     Form(form): Form<SendOnchainActionForm>,
 ) -> Result<Response, StatusCode> {
-    let encoded_form =
-        serde_urlencoded::to_string(&form).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
+    let mut serializer = url::form_urlencoded::Serializer::new(String::new());
+    serializer.append_pair("address", &form.address);
+    if let Some(amount) = form.amount_sat {
+        serializer.append_pair("amount_sat", &amount.to_string());
+    }
+    serializer.append_pair("send_action", &form.send_action);
+    let encoded_form = serializer.finish();
 
     Response::builder()
         .status(StatusCode::FOUND)

+ 1 - 1
crates/cdk-npubcash/Cargo.toml

@@ -13,7 +13,7 @@ async-trait = { workspace = true }
 cashu = { workspace = true }
 cdk-common = { workspace = true, features = ["wallet"] }
 nostr-sdk = { workspace = true }
-reqwest = { workspace = true }
+bitreq = { workspace = true }
 serde = { workspace = true }
 serde_json = { workspace = true }
 thiserror = { workspace = true }

+ 14 - 22
crates/cdk-npubcash/src/auth.rs

@@ -6,6 +6,7 @@ use std::sync::Arc;
 use std::time::{Duration, SystemTime};
 
 use base64::Engine;
+use bitreq::Response;
 use nostr_sdk::{EventBuilder, Keys, Kind, Tag};
 use tokio::sync::RwLock;
 
@@ -23,7 +24,6 @@ struct CachedToken {
 pub struct JwtAuthProvider {
     base_url: String,
     keys: Keys,
-    http_client: reqwest::Client,
     cached_token: Arc<RwLock<Option<CachedToken>>>,
 }
 
@@ -38,7 +38,6 @@ impl JwtAuthProvider {
         Self {
             base_url,
             keys,
-            http_client: reqwest::Client::new(),
             cached_token: Arc::new(RwLock::new(None)),
         }
     }
@@ -104,44 +103,37 @@ impl JwtAuthProvider {
     }
 
     /// Send the authentication request to the API
-    async fn send_auth_request(
-        &self,
-        auth_url: &str,
-        nostr_token: &str,
-    ) -> Result<reqwest::Response> {
+    async fn send_auth_request(&self, auth_url: &str, nostr_token: &str) -> Result<Response> {
         tracing::debug!("Sending request to: {}", auth_url);
         tracing::debug!(
             "Authorization header: Nostr {}",
             &nostr_token[..50.min(nostr_token.len())]
         );
 
-        let response = self
-            .http_client
-            .get(auth_url)
-            .header("Authorization", format!("Nostr {nostr_token}"))
-            .header("Content-Type", "application/json")
-            .header("Accept", "application/json")
-            .header("User-Agent", "cdk-npubcash/0.13.0")
-            .send()
+        let response = bitreq::get(auth_url)
+            .with_header("Authorization", format!("Nostr {nostr_token}"))
+            .with_header("Content-Type", "application/json")
+            .with_header("Accept", "application/json")
+            .with_header("User-Agent", "cdk-npubcash/0.13.0")
+            .send_async()
             .await?;
 
-        tracing::debug!("Response status: {}", response.status());
+        tracing::debug!("Response status: {}", response.status_code);
         Ok(response)
     }
 
     /// Parse the JWT response from the API
-    async fn parse_jwt_response(&self, response: reqwest::Response) -> Result<String> {
-        let status = response.status();
-
-        if !status.is_success() {
-            let error_text = response.text().await.unwrap_or_default();
+    async fn parse_jwt_response(&self, response: Response) -> Result<String> {
+        let status = response.status_code;
+        if status != 200 {
+            let error_text = response.as_str().unwrap_or_default();
             tracing::error!("Auth failed - Status: {}, Body: {}", status, error_text);
             return Err(Error::Auth(format!(
                 "Failed to get JWT: {status} - {error_text}"
             )));
         }
 
-        let nip98_response: Nip98Response = response.json().await?;
+        let nip98_response: Nip98Response = response.json()?;
         Ok(nip98_response.data.token)
     }
 

+ 28 - 35
crates/cdk-npubcash/src/client.rs

@@ -2,7 +2,7 @@
 
 use std::sync::Arc;
 
-use reqwest::Client as HttpClient;
+use bitreq::Response;
 use tracing::instrument;
 
 use crate::auth::JwtAuthProvider;
@@ -17,7 +17,6 @@ const THROTTLE_DELAY_MS: u64 = 200;
 pub struct NpubCashClient {
     base_url: String,
     auth_provider: Arc<JwtAuthProvider>,
-    http_client: HttpClient,
 }
 
 impl std::fmt::Debug for NpubCashClient {
@@ -40,7 +39,6 @@ impl NpubCashClient {
         Self {
             base_url,
             auth_provider,
-            http_client: HttpClient::new(),
         }
     }
 
@@ -186,34 +184,31 @@ impl NpubCashClient {
         let auth_header = self.auth_provider.get_nip98_auth_header(&url, "PATCH")?;
 
         // Send PATCH request
-        let response = self
-            .http_client
-            .patch(&url)
-            .header("Authorization", auth_header)
-            .header("Content-Type", "application/json")
-            .header("Accept", "application/json")
-            .header("User-Agent", "cdk-npubcash/0.13.0")
-            .json(&payload)
-            .send()
+        let response = bitreq::patch(&url)
+            .with_header("Authorization", auth_header)
+            .with_header("Content-Type", "application/json")
+            .with_header("Accept", "application/json")
+            .with_header("User-Agent", "cdk-npubcash/0.13.0")
+            .with_json(&payload)?
+            .send_async()
             .await?;
 
-        let status = response.status();
+        let status = response.status_code;
+        let response_text = response.as_str().unwrap_or_default();
 
         // Handle error responses
-        if !status.is_success() {
-            let error_text = response.text().await.unwrap_or_default();
+        if status != 200 {
             return Err(Error::Api {
-                message: error_text,
-                status: status.as_u16(),
+                message: response_text.to_owned(),
+                status: status as u16,
             });
         }
 
         // Get response text for debugging
-        let response_text = response.text().await?;
         tracing::debug!("set_mint_url response: {}", response_text);
 
         // Parse JSON response
-        serde_json::from_str(&response_text).map_err(|e| {
+        serde_json::from_str(response_text).map_err(|e| {
             tracing::error!("Failed to parse response: {} - Body: {}", e, response_text);
             Error::Custom(format!("JSON parse error: {e}"))
         })
@@ -257,44 +252,42 @@ impl NpubCashClient {
 
         // Send the HTTP request with authentication headers
         tracing::debug!("Making {} request to {}", method, url);
-        let response = self
-            .http_client
-            .get(url)
-            .header("Authorization", auth_token)
-            .header("Content-Type", "application/json")
-            .header("Accept", "application/json")
-            .header("User-Agent", "cdk-npubcash/0.13.0")
-            .send()
+        let response = bitreq::get(url)
+            .with_header("Authorization", auth_token)
+            .with_header("Content-Type", "application/json")
+            .with_header("Accept", "application/json")
+            .with_header("User-Agent", "cdk-npubcash/0.13.0")
+            .send_async()
             .await?;
 
-        tracing::debug!("Response status: {}", response.status());
+        tracing::debug!("Response status: {}", response.status_code);
 
         // Parse and return the JSON response
         self.parse_response(response).await
     }
 
     /// Parse the HTTP response and deserialize the JSON body
-    async fn parse_response<T>(&self, response: reqwest::Response) -> Result<T>
+    async fn parse_response<T>(&self, response: Response) -> Result<T>
     where
         T: serde::de::DeserializeOwned,
     {
-        let status = response.status();
+        let status = response.status_code;
 
         // Get the response text
-        let response_text = response.text().await?;
+        let response_text = response.as_str().unwrap_or_default();
 
         // Handle error status codes
-        if !status.is_success() {
+        if status != 200 {
             tracing::debug!("Error response ({}): {}", status, response_text);
             return Err(Error::Api {
-                message: response_text,
-                status: status.as_u16(),
+                message: response_text.to_owned(),
+                status: status as u16,
             });
         }
 
         // Parse successful JSON response
         tracing::debug!("Response body: {}", response_text);
-        let data = serde_json::from_str::<T>(&response_text).map_err(|e| {
+        let data = serde_json::from_str::<T>(response_text).map_err(|e| {
             tracing::error!("JSON parse error: {} - Body: {}", e, response_text);
             Error::Custom(format!("JSON parse error: {e}"))
         })?;

+ 1 - 1
crates/cdk-npubcash/src/error.rs

@@ -23,7 +23,7 @@ pub enum Error {
 
     /// HTTP request failed
     #[error("HTTP request failed: {0}")]
-    Http(#[from] reqwest::Error),
+    Http(#[from] bitreq::Error),
 
     /// JSON serialization/deserialization error
     #[error("JSON serialization error: {0}")]

+ 2 - 2
crates/cdk-prometheus/Cargo.toml

@@ -1,5 +1,5 @@
 [package]
-name = "cdk-prometheus" 
+name = "cdk-prometheus"
 version.workspace = true
 edition.workspace = true
 rust-version.workspace = true
@@ -40,7 +40,7 @@ once_cell.workspace = true
 
 [dev-dependencies]
 tokio = { workspace = true, features = ["full"] }
-reqwest.workspace = true
+bitreq.workspace = true
 tracing-subscriber.workspace = true
 
 [lints]

+ 4 - 4
crates/cdk/Cargo.toml

@@ -12,10 +12,10 @@ license.workspace = true
 
 [features]
 default = ["mint", "wallet", "auth", "nostr", "bip353"]
-wallet = ["dep:futures", "dep:reqwest", "cdk-common/wallet", "dep:rustls"]
+wallet = ["dep:futures", "dep:bitreq", "cdk-common/wallet", "dep:rustls"]
 nostr = ["wallet", "dep:nostr-sdk", "cdk-common/nostr"]
 npubcash = ["wallet", "nostr", "dep:cdk-npubcash"]
-mint = ["dep:futures", "dep:reqwest", "cdk-common/mint", "cdk-signatory"]
+mint = ["dep:futures", "dep:bitreq", "cdk-common/mint", "cdk-signatory"]
 auth = ["dep:jsonwebtoken", "cdk-common/auth", "cdk-common/auth"]
 bip353 = ["dep:hickory-resolver"]
 # We do not commit to a MSRV with swagger enabled
@@ -46,7 +46,7 @@ ciborium.workspace = true
 lightning.workspace = true
 lightning-invoice.workspace = true
 regex.workspace = true
-reqwest = { workspace = true, optional = true }
+bitreq = { workspace = true, optional = true }
 serde.workspace = true
 serde_json.workspace = true
 serde_with.workspace = true
@@ -175,7 +175,7 @@ cdk-fake-wallet.workspace = true
 bip39.workspace = true
 tracing-subscriber.workspace = true
 criterion.workspace = true
-reqwest = { workspace = true }
+bitreq = { workspace = true }
 anyhow.workspace = true
 ureq = { version = "3.1.0", features = ["json"] }
 tokio = { workspace = true, features = ["full"] }

+ 8 - 9
crates/cdk/examples/auth_wallet.rs

@@ -120,18 +120,17 @@ async fn get_access_token(mint_info: &MintInfo) -> String {
     ];
 
     // Make the token request directly
-    let client = reqwest::Client::new();
-    let response = client
-        .post(token_url)
-        .form(&params)
-        .send()
+    let params = url::form_urlencoded::Serializer::new(String::new())
+        .extend_pairs(&params)
+        .finish();
+    let response = bitreq::post(token_url)
+        .with_body(params)
+        .send_async()
         .await
         .expect("Failed to send token request");
 
-    let token_response: serde_json::Value = response
-        .json()
-        .await
-        .expect("Failed to parse token response");
+    let token_response: serde_json::Value =
+        response.json().expect("Failed to parse token response");
 
     token_response["access_token"]
         .as_str()

+ 2 - 5
crates/cdk/examples/multimint-npubcash.rs

@@ -143,11 +143,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
 
 /// Request an invoice via LNURL-pay
 async fn request_invoice(npub: &str, amount_msats: u64) -> Result<(), Box<dyn std::error::Error>> {
-    let http_client = reqwest::Client::new();
-
     let lnurlp_url = format!("{}/.well-known/lnurlp/{}", NPUBCASH_URL, npub);
-    let lnurlp_response: serde_json::Value =
-        http_client.get(&lnurlp_url).send().await?.json().await?;
+    let lnurlp_response: serde_json::Value = bitreq::get(&lnurlp_url).send_async().await?.json()?;
 
     let callback = lnurlp_response["callback"]
         .as_str()
@@ -155,7 +152,7 @@ async fn request_invoice(npub: &str, amount_msats: u64) -> Result<(), Box<dyn st
 
     let invoice_url = format!("{}?amount={}", callback, amount_msats);
     let invoice_response: serde_json::Value =
-        http_client.get(&invoice_url).send().await?.json().await?;
+        bitreq::get(&invoice_url).send_async().await?.json()?;
 
     let pr = invoice_response["pr"]
         .as_str()

+ 2 - 5
crates/cdk/examples/npubcash.rs

@@ -136,11 +136,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
 
 /// Request an invoice via LNURL-pay
 async fn request_invoice(npub: &str, amount_msats: u64) -> Result<(), Box<dyn std::error::Error>> {
-    let http_client = reqwest::Client::new();
-
     let lnurlp_url = format!("{}/.well-known/lnurlp/{}", NPUBCASH_URL, npub);
-    let lnurlp_response: serde_json::Value =
-        http_client.get(&lnurlp_url).send().await?.json().await?;
+    let lnurlp_response: serde_json::Value = bitreq::get(&lnurlp_url).send_async().await?.json()?;
 
     let callback = lnurlp_response["callback"]
         .as_str()
@@ -148,7 +145,7 @@ async fn request_invoice(npub: &str, amount_msats: u64) -> Result<(), Box<dyn st
 
     let invoice_url = format!("{}?amount={}", callback, amount_msats);
     let invoice_response: serde_json::Value =
-        http_client.get(&invoice_url).send().await?.json().await?;
+        bitreq::get(&invoice_url).send_async().await?.json()?;
 
     let pr = invoice_response["pr"]
         .as_str()

+ 40 - 23
crates/cdk/src/oidc_client.rs

@@ -1,12 +1,13 @@
 //! Open Id Connect
 
 use std::collections::HashMap;
+use std::fmt::Debug;
 use std::ops::Deref;
 use std::sync::Arc;
 
+use bitreq::{Client, RequestExt};
 use jsonwebtoken::jwk::{AlgorithmParameters, JwkSet};
 use jsonwebtoken::{decode, decode_header, DecodingKey, Validation};
-use reqwest::Client;
 use serde::Deserialize;
 #[cfg(feature = "wallet")]
 use serde::Serialize;
@@ -19,7 +20,7 @@ use tracing::instrument;
 pub enum Error {
     /// From Reqwest error
     #[error(transparent)]
-    Reqwest(#[from] reqwest::Error),
+    Reqwest(#[from] bitreq::Error),
     /// From Reqwest error
     #[error(transparent)]
     Jwt(#[from] jsonwebtoken::errors::Error),
@@ -54,7 +55,7 @@ pub struct OidcConfig {
 }
 
 /// Http Client
-#[derive(Debug, Clone)]
+#[derive(Clone)]
 pub struct OidcClient {
     client: Client,
     openid_discovery: String,
@@ -63,6 +64,16 @@ pub struct OidcClient {
     jwks_set: Arc<RwLock<Option<JwkSet>>>,
 }
 
+impl Debug for OidcClient {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "OidcClient({:?}): {}",
+            self.client_id, self.openid_discovery
+        )
+    }
+}
+
 #[cfg(feature = "wallet")]
 #[derive(Debug, Clone, Copy, Serialize)]
 #[serde(rename_all = "snake_case")]
@@ -71,6 +82,15 @@ pub enum GrantType {
 }
 
 #[cfg(feature = "wallet")]
+impl GrantType {
+    fn as_str(&self) -> &'static str {
+        match self {
+            GrantType::RefreshToken => "refresh_token",
+        }
+    }
+}
+
+#[cfg(feature = "wallet")]
 #[derive(Debug, Clone, Serialize)]
 pub struct RefreshTokenRequest {
     pub grant_type: GrantType,
@@ -91,7 +111,7 @@ impl OidcClient {
     /// Create new [`OidcClient`]
     pub fn new(openid_discovery: String, client_id: Option<String>) -> Self {
         Self {
-            client: Client::new(),
+            client: Client::new(10),
             openid_discovery,
             client_id,
             oidc_config: Arc::new(RwLock::new(None)),
@@ -103,13 +123,10 @@ impl OidcClient {
     #[instrument(skip(self))]
     pub async fn get_oidc_config(&self) -> Result<OidcConfig, Error> {
         tracing::debug!("Getting oidc config");
-        let oidc_config = self
-            .client
-            .get(&self.openid_discovery)
-            .send()
+        let oidc_config = bitreq::get(&self.openid_discovery)
+            .send_async_with_client(&self.client)
             .await?
-            .json::<OidcConfig>()
-            .await?;
+            .json::<OidcConfig>()?;
 
         let mut current_config = self.oidc_config.write().await;
 
@@ -122,13 +139,10 @@ impl OidcClient {
     #[instrument(skip(self))]
     pub async fn get_jwkset(&self, jwks_uri: &str) -> Result<JwkSet, Error> {
         tracing::debug!("Getting jwks set");
-        let jwks_set = self
-            .client
-            .get(jwks_uri)
-            .send()
+        let jwks_set = bitreq::get(jwks_uri)
+            .send_async_with_client(&self.client)
             .await?
-            .json::<JwkSet>()
-            .await?;
+            .json::<JwkSet>()?;
 
         let mut current_set = self.jwks_set.write().await;
 
@@ -248,14 +262,17 @@ impl OidcClient {
             refresh_token,
         };
 
-        let response = self
-            .client
-            .post(token_url)
-            .form(&request)
-            .send()
+        let body = url::form_urlencoded::Serializer::new(String::new())
+            .append_pair("grant_type", request.grant_type.as_str())
+            .append_pair("client_id", &request.client_id)
+            .append_pair("refresh_token", &request.refresh_token)
+            .finish();
+
+        let response = bitreq::post(token_url)
+            .with_body(body)
+            .send_async_with_client(&self.client)
             .await?
-            .json::<TokenResponse>()
-            .await?;
+            .json::<TokenResponse>()?;
 
         Ok(response)
     }

+ 98 - 66
crates/cdk/src/wallet/mint_connector/transport.rs

@@ -1,6 +1,8 @@
 //! HTTP Transport trait with a default implementation
+use std::collections::HashMap;
 use std::fmt::Debug;
 
+use bitreq::{Client, Proxy, Request, RequestExt};
 use cdk_common::AuthToken;
 #[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
 use hickory_resolver::config::ResolverConfig;
@@ -8,7 +10,7 @@ use hickory_resolver::config::ResolverConfig;
 use hickory_resolver::name_server::TokioConnectionProvider;
 #[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
 use hickory_resolver::Resolver;
-use reqwest::Client;
+use regex::Regex;
 use serde::de::DeserializeOwned;
 use serde::Serialize;
 use url::Url;
@@ -53,10 +55,56 @@ pub trait Transport: Default + Send + Sync + Debug + Clone {
         R: serde::de::DeserializeOwned;
 }
 
-/// Async transport for Http
 #[derive(Debug, Clone)]
+struct ProxyWrapper {
+    proxy: Proxy,
+    _accept_invalid_certs: bool,
+}
+
+/// Async transport for Http
+#[derive(Clone)]
 pub struct Async {
-    inner: Client,
+    client: Client,
+    proxy_per_url: HashMap<String, (Regex, ProxyWrapper)>,
+    all_proxy: Option<ProxyWrapper>,
+}
+
+impl Async {
+    fn prepare_request(&self, req: Request, url: Url, auth: Option<AuthToken>) -> Request {
+        let proxy = {
+            let url = url.to_string();
+            let mut proxy = None;
+            for (pattern, proxy_wrapper) in self.proxy_per_url.values() {
+                if pattern.is_match(&url) {
+                    proxy = Some(proxy_wrapper.proxy.clone());
+                }
+            }
+
+            if proxy.is_some() {
+                proxy
+            } else {
+                self.all_proxy.as_ref().map(|x| x.proxy.clone())
+            }
+        };
+
+        let request = if let Some(proxy) = proxy {
+            req.with_proxy(proxy)
+        } else {
+            req
+        };
+
+        if let Some(auth) = auth {
+            request.with_header(auth.header_key(), auth.to_string())
+        } else {
+            request
+        }
+    }
+}
+
+impl Debug for Async {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "HTTP Async client")
+    }
 }
 
 impl Default for Async {
@@ -67,7 +115,9 @@ impl Default for Async {
         }
 
         Self {
-            inner: Client::new(),
+            client: Client::new(10),
+            proxy_per_url: HashMap::new(),
+            all_proxy: None,
         }
     }
 }
@@ -92,27 +142,23 @@ impl Transport for Async {
         host_matcher: Option<&str>,
         accept_invalid_certs: bool,
     ) -> Result<(), Error> {
-        let builder = reqwest::Client::builder().danger_accept_invalid_certs(accept_invalid_certs);
-
-        let builder = match host_matcher {
-            Some(pattern) => {
-                // When a matcher is provided, only apply the proxy to matched hosts
-                let regex = regex::Regex::new(pattern).map_err(|e| Error::Custom(e.to_string()))?;
-                builder.proxy(reqwest::Proxy::custom(move |url| {
-                    url.host_str()
-                        .filter(|host| regex.is_match(host))
-                        .map(|_| proxy.clone())
-                }))
-            }
-            // Apply proxy to all requests when no matcher is provided
-            None => {
-                builder.proxy(reqwest::Proxy::all(proxy).map_err(|e| Error::Custom(e.to_string()))?)
-            }
+        let proxy = ProxyWrapper {
+            proxy: bitreq::Proxy::new_http(proxy).map_err(|_| Error::Internal)?,
+            _accept_invalid_certs: accept_invalid_certs,
         };
+        if let Some((key, pattern)) = host_matcher
+            .map(|pattern| {
+                regex::Regex::new(pattern)
+                    .map(|regex| (pattern.to_owned(), regex))
+                    .map_err(|e| Error::Custom(e.to_string()))
+            })
+            .transpose()?
+        {
+            self.proxy_per_url.insert(key, (pattern, proxy));
+        } else {
+            self.all_proxy = Some(proxy);
+        }
 
-        self.inner = builder
-            .build()
-            .map_err(|e| Error::HttpError(e.status().map(|s| s.as_u16()), e.to_string()))?;
         Ok(())
     }
 
@@ -144,33 +190,22 @@ impl Transport for Async {
     where
         R: DeserializeOwned,
     {
-        let mut request = self.inner.get(url);
+        let response = self
+            .prepare_request(bitreq::get(url.clone()), url, auth)
+            .send_async_with_client(&self.client)
+            .await
+            .map_err(|e| Error::HttpError(None, e.to_string()))?;
 
-        if let Some(auth) = auth {
-            request = request.header(auth.header_key(), auth.to_string());
+        if response.status_code != 200 {
+            return Err(Error::HttpError(
+                Some(response.status_code as u16),
+                "".to_string(),
+            ));
         }
 
-        let response = request
-            .send()
-            .await
-            .map_err(|e| {
-                Error::HttpError(
-                    e.status().map(|status_code| status_code.as_u16()),
-                    e.to_string(),
-                )
-            })?
-            .text()
-            .await
-            .map_err(|e| {
-                Error::HttpError(
-                    e.status().map(|status_code| status_code.as_u16()),
-                    e.to_string(),
-                )
-            })?;
-
-        serde_json::from_str::<R>(&response).map_err(|err| {
+        serde_json::from_slice::<R>(response.as_bytes()).map_err(|err| {
             tracing::warn!("Http Response error: {}", err);
-            match ErrorResponse::from_json(&response) {
+            match ErrorResponse::from_slice(response.as_bytes()) {
                 Ok(ok) => <ErrorResponse as Into<Error>>::into(ok),
                 Err(err) => err.into(),
             }
@@ -187,30 +222,27 @@ impl Transport for Async {
         P: Serialize + ?Sized + Send + Sync,
         R: DeserializeOwned,
     {
-        let mut request = self.inner.post(url).json(&payload);
-
-        if let Some(auth) = auth_token {
-            request = request.header(auth.header_key(), auth.to_string());
-        }
-
-        let response = request.send().await.map_err(|e| {
-            Error::HttpError(
-                e.status().map(|status_code| status_code.as_u16()),
-                e.to_string(),
+        let response = self
+            .prepare_request(bitreq::post(url.clone()), url, auth_token)
+            .with_body(serde_json::to_string(payload).map_err(Error::SerdeJsonError)?)
+            .with_header(
+                "Content-Type".to_string(),
+                "application/json; charset=UTF-8".to_string(),
             )
-        })?;
+            .send_async_with_client(&self.client)
+            .await
+            .map_err(|e| Error::HttpError(None, e.to_string()))?;
 
-        let response = response.text().await.map_err(|e| {
-            Error::HttpError(
-                e.status().map(|status_code| status_code.as_u16()),
-                e.to_string(),
-            )
-        })?;
+        if response.status_code != 200 {
+            return Err(Error::HttpError(
+                Some(response.status_code as u16),
+                "".to_string(),
+            ));
+        }
 
-        serde_json::from_str::<R>(&response).map_err(|err| {
+        serde_json::from_slice::<R>(response.as_bytes()).map_err(|err| {
             tracing::warn!("Http Response error: {}", err);
-            tracing::debug!("{:?}", response);
-            match ErrorResponse::from_json(&response) {
+            match ErrorResponse::from_slice(response.as_bytes()) {
                 Ok(ok) => <ErrorResponse as Into<Error>>::into(ok),
                 Err(err) => err.into(),
             }

+ 7 - 11
crates/cdk/src/wallet/payment_request.rs

@@ -15,7 +15,6 @@ use nostr_sdk::nips::nip19::Nip19Profile;
 use nostr_sdk::prelude::*;
 #[cfg(feature = "nostr")]
 use nostr_sdk::{Client as NostrClient, EventBuilder, FromBech32, Keys, ToBech32};
-use reqwest::Client;
 
 use crate::error::Error;
 use crate::mint_url::MintUrl;
@@ -164,22 +163,19 @@ impl Wallet {
                 }
 
                 TransportType::HttpPost => {
-                    let client = Client::new();
-
-                    let res = client
-                        .post(transport.target.clone())
-                        .json(&payload)
-                        .send()
+                    let response = bitreq::post(transport.target.clone())
+                        .with_body(serde_json::to_string(&payload).map_err(Error::SerdeJsonError)?)
+                        .send_async()
                         .await
                         .map_err(|e| Error::HttpError(None, e.to_string()))?;
 
-                    let status = res.status();
-                    if status.is_success() {
+                    if response.status_code == 200 {
                         println!("Successfully posted payment");
                         Ok(())
                     } else {
-                        let body = res.text().await.unwrap_or_default();
-                        Err(Error::HttpError(Some(status.as_u16()), body))
+                        let body = serde_json::from_slice(response.as_bytes())
+                            .map_err(Error::SerdeJsonError)?;
+                        Err(Error::HttpError(Some(response.status_code as u16), body))
                     }
                 }
                 TransportType::InBand => {