Prechádzať zdrojové kódy

Expose the traits to create custom HTTP transport

Add a wallet example with ureq, another rust crate for http request (only
included for example)
Cesar Rodas 2 mesiacov pred
rodič
commit
0ab76419f2

+ 6 - 0
crates/cdk/Cargo.toml

@@ -106,6 +106,11 @@ required-features = ["wallet", "bip353"]
 [[example]]
 name = "mint-token-bolt12-with-stream"
 required-features = ["wallet"]
+
+[[example]]
+name = "mint-token-bolt12-with-custom-http"
+required-features = ["wallet"]
+
 [[example]]
 name = "mint-token-bolt12"
 required-features = ["wallet"]
@@ -118,6 +123,7 @@ tracing-subscriber.workspace = true
 criterion = "0.6.0"
 reqwest = { workspace = true }
 anyhow.workspace = true
+ureq = { version = "3.1.0", features = ["json"] }
 
 
 [[bench]]

+ 161 - 0
crates/cdk/examples/mint-token-bolt12-with-custom-http.rs

@@ -0,0 +1,161 @@
+use std::str::FromStr;
+use std::sync::Arc;
+use std::time::Duration;
+
+use cdk::error::Error;
+use cdk::nuts::nut00::ProofsMethods;
+use cdk::nuts::CurrencyUnit;
+use cdk::wallet::{BaseHttpClient, HttpTransport, SendOptions, WalletBuilder};
+use cdk::{Amount, StreamExt};
+use cdk_common::mint_url::MintUrl;
+use cdk_common::AuthToken;
+use cdk_sqlite::wallet::memory;
+use rand::random;
+use serde::de::DeserializeOwned;
+use serde::Serialize;
+use tracing_subscriber::EnvFilter;
+use ureq::config::Config;
+use ureq::Agent;
+use url::Url;
+
+#[derive(Debug, Clone)]
+pub struct CustomHttp {
+    agent: Agent,
+}
+
+impl Default for CustomHttp {
+    fn default() -> Self {
+        Self {
+            agent: Agent::new_with_config(
+                Config::builder()
+                    .timeout_global(Some(Duration::from_secs(5)))
+                    .no_delay(true)
+                    .user_agent("Custom HTTP Transport")
+                    .build(),
+            ),
+        }
+    }
+}
+
+#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
+#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
+impl HttpTransport for CustomHttp {
+    fn with_proxy(
+        &mut self,
+        _proxy: Url,
+        _host_matcher: Option<&str>,
+        _accept_invalid_certs: bool,
+    ) -> Result<(), Error> {
+        panic!("Not supported");
+    }
+
+    async fn http_get<R>(&self, url: Url, _auth: Option<AuthToken>) -> Result<R, Error>
+    where
+        R: DeserializeOwned,
+    {
+        self.agent
+            .get(url.as_str())
+            .call()
+            .map_err(|e| Error::HttpError(None, e.to_string()))?
+            .body_mut()
+            .read_json()
+            .map_err(|e| Error::HttpError(None, e.to_string()))
+    }
+
+    /// HTTP Post request
+    async fn http_post<P, R>(
+        &self,
+        url: Url,
+        _auth_token: Option<AuthToken>,
+        payload: &P,
+    ) -> Result<R, Error>
+    where
+        P: Serialize + ?Sized + Send + Sync,
+        R: DeserializeOwned,
+    {
+        self.agent
+            .post(url.as_str())
+            .send_json(payload)
+            .map_err(|e| Error::HttpError(None, e.to_string()))?
+            .body_mut()
+            .read_json()
+            .map_err(|e| Error::HttpError(None, e.to_string()))
+    }
+}
+
+type CustomConnector = BaseHttpClient<CustomHttp>;
+
+#[tokio::main]
+async fn main() -> Result<(), Error> {
+    let default_filter = "debug";
+
+    let sqlx_filter = "sqlx=warn,hyper_util=warn,reqwest=warn,rustls=warn";
+
+    let env_filter = EnvFilter::new(format!("{},{}", default_filter, sqlx_filter));
+
+    // Parse input
+    tracing_subscriber::fmt().with_env_filter(env_filter).init();
+
+    // Initialize the memory store for the wallet
+    let localstore = Arc::new(memory::empty().await?);
+
+    // Generate a random seed for the wallet
+    let seed = random::<[u8; 64]>();
+
+    // Define the mint URL and currency unit
+    let mint_url = "https://fake.thesimplekid.dev";
+    let unit = CurrencyUnit::Sat;
+    let amount = Amount::from(10);
+
+    let mint_url = MintUrl::from_str(mint_url)?;
+    #[cfg(feature = "auth")]
+    let http_client = CustomConnector::new(mint_url.clone(), None);
+
+    #[cfg(not(feature = "auth"))]
+    let http_client = CustomConnector::new(mint_url.clone());
+
+    // Create a new wallet
+    let wallet = WalletBuilder::new()
+        .mint_url(mint_url)
+        .unit(unit)
+        .localstore(localstore)
+        .seed(seed)
+        .target_proof_count(3)
+        .client(http_client)
+        .build()?;
+
+    let quotes = vec![
+        wallet.mint_bolt12_quote(None, None).await?,
+        wallet.mint_bolt12_quote(None, None).await?,
+        wallet.mint_bolt12_quote(None, None).await?,
+    ];
+
+    let mut stream = wallet.mints_proof_stream(quotes, Default::default(), None);
+
+    let stop = stream.get_cancel_token();
+
+    let mut processed = 0;
+
+    while let Some(proofs) = stream.next().await {
+        let (mint_quote, proofs) = proofs?;
+
+        // Mint the received amount
+        let receive_amount = proofs.total_amount()?;
+        tracing::info!("Received {} from mint {}", receive_amount, mint_quote.id);
+
+        // Send a token with the specified amount
+        let prepared_send = wallet.prepare_send(amount, SendOptions::default()).await?;
+        let token = prepared_send.confirm(None).await?;
+        tracing::info!("Token: {}", token);
+
+        processed += 1;
+
+        if processed == 3 {
+            stop.cancel()
+        }
+    }
+
+    tracing::info!("Stopped the loop after {} quotes being minted", processed);
+
+    Ok(())
+}

+ 5 - 10
crates/cdk/src/wallet/mint_connector/http_client.rs

@@ -1,3 +1,4 @@
+//! HTTP Mint client with pluggable transport
 use std::collections::HashSet;
 use std::sync::{Arc, RwLock as StdRwLock};
 use std::time::{Duration, Instant};
@@ -193,7 +194,7 @@ where
 
         Ok(self
             .transport
-            .http_get::<_, KeysResponse>(url, None)
+            .http_get::<KeysResponse>(url, None)
             .await?
             .keysets)
     }
@@ -205,10 +206,7 @@ where
             .mint_url
             .join_paths(&["v1", "keys", &keyset_id.to_string()])?;
 
-        let keys_response = self
-            .transport
-            .http_get::<_, KeysResponse>(url, None)
-            .await?;
+        let keys_response = self.transport.http_get::<KeysResponse>(url, None).await?;
 
         Ok(keys_response.keysets.first().unwrap().clone())
     }
@@ -574,7 +572,7 @@ where
     /// Get Mint Info [NUT-06]
     async fn get_mint_info(&self) -> Result<MintInfo, Error> {
         let url = self.mint_url.join_paths(&["v1", "info"])?;
-        let mint_info: MintInfo = self.transport.http_get::<_, MintInfo>(url, None).await?;
+        let mint_info: MintInfo = self.transport.http_get::<MintInfo>(url, None).await?;
 
         Ok(mint_info)
     }
@@ -586,10 +584,7 @@ where
             self.mint_url
                 .join_paths(&["v1", "auth", "blind", "keys", &keyset_id.to_string()])?;
 
-        let mut keys_response = self
-            .transport
-            .http_get::<_, KeysResponse>(url, None)
-            .await?;
+        let mut keys_response = self.transport.http_get::<KeysResponse>(url, None).await?;
 
         let keyset = keys_response
             .keysets

+ 1 - 1
crates/cdk/src/wallet/mint_connector/mod.rs

@@ -15,7 +15,7 @@ use crate::nuts::{
 #[cfg(feature = "auth")]
 use crate::wallet::AuthWallet;
 
-mod http_client;
+pub mod http_client;
 pub mod transport;
 
 /// Auth HTTP Client with async transport

+ 10 - 11
crates/cdk/src/wallet/mint_connector/transport.rs

@@ -2,7 +2,7 @@
 use std::fmt::Debug;
 
 use cdk_common::AuthToken;
-use reqwest::{Client, IntoUrl};
+use reqwest::Client;
 use serde::de::DeserializeOwned;
 use serde::Serialize;
 use url::Url;
@@ -14,6 +14,7 @@ use crate::error::ErrorResponse;
 #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
 #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
 pub trait Transport: Default + Send + Sync + Debug + Clone {
+    /// Make the transport to use a given proxy
     fn with_proxy(
         &mut self,
         proxy: Url,
@@ -21,19 +22,19 @@ pub trait Transport: Default + Send + Sync + Debug + Clone {
         accept_invalid_certs: bool,
     ) -> Result<(), Error>;
 
-    async fn http_get<U, R>(&self, url: U, auth: Option<AuthToken>) -> Result<R, Error>
+    /// HTTP Get request
+    async fn http_get<R>(&self, url: Url, auth: Option<AuthToken>) -> Result<R, Error>
     where
-        U: IntoUrl + Send,
         R: DeserializeOwned;
 
-    async fn http_post<U, P, R>(
+    /// HTTP Post request
+    async fn http_post<P, R>(
         &self,
-        url: U,
+        url: Url,
         auth_token: Option<AuthToken>,
         payload: &P,
     ) -> Result<R, Error>
     where
-        U: IntoUrl + Send + Sync,
         P: Serialize + ?Sized + Send + Sync,
         R: DeserializeOwned;
 }
@@ -103,9 +104,8 @@ impl Transport for Async {
         Ok(())
     }
 
-    async fn http_get<U, R>(&self, url: U, auth: Option<AuthToken>) -> Result<R, Error>
+    async fn http_get<R>(&self, url: Url, auth: Option<AuthToken>) -> Result<R, Error>
     where
-        U: IntoUrl + Send,
         R: DeserializeOwned,
     {
         let mut request = self.inner.get(url);
@@ -141,14 +141,13 @@ impl Transport for Async {
         })
     }
 
-    async fn http_post<U, P, R>(
+    async fn http_post<P, R>(
         &self,
-        url: U,
+        url: Url,
         auth_token: Option<AuthToken>,
         payload: &P,
     ) -> Result<R, Error>
     where
-        U: IntoUrl + Send + Sync,
         P: Serialize + ?Sized + Send + Sync,
         R: DeserializeOwned,
     {

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

@@ -54,6 +54,10 @@ pub use auth::{AuthMintConnector, AuthWallet};
 pub use builder::WalletBuilder;
 pub use cdk_common::wallet as types;
 #[cfg(feature = "auth")]
+pub use mint_connector::http_client::AuthHttpClient as BaseAuthHttpClient;
+pub use mint_connector::http_client::HttpClient as BaseHttpClient;
+pub use mint_connector::transport::Transport as HttpTransport;
+#[cfg(feature = "auth")]
 pub use mint_connector::AuthHttpClient;
 pub use mint_connector::{HttpClient, MintConnector};
 pub use multi_mint_wallet::MultiMintWallet;