| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712 |
- //! HTTP Mint client with pluggable transport
- use std::collections::HashSet;
- use std::sync::{Arc, RwLock as StdRwLock};
- use async_trait::async_trait;
- use cdk_common::{
- nut19, MeltQuoteBolt12Request, MeltQuoteBolt12Response, MeltQuoteCustomResponse, Method,
- MintQuoteBolt12Request, MintQuoteBolt12Response, ProtectedEndpoint, RoutePath,
- };
- use serde::de::DeserializeOwned;
- use serde::Serialize;
- use tokio::sync::RwLock;
- use tracing::instrument;
- use url::Url;
- use web_time::{Duration, Instant};
- use super::transport::Transport;
- use super::{Error, MeltOptions, MintConnector};
- use crate::mint_url::MintUrl;
- use crate::nuts::nut00::{KnownMethod, PaymentMethod};
- use crate::nuts::nut22::MintAuthRequest;
- use crate::nuts::{
- AuthToken, CheckStateRequest, CheckStateResponse, Id, KeySet, KeysResponse, KeysetResponse,
- MeltQuoteBolt11Request, MeltQuoteBolt11Response, MeltQuoteCustomRequest, MeltRequest, MintInfo,
- MintQuoteBolt11Request, MintQuoteBolt11Response, MintQuoteCustomRequest,
- MintQuoteCustomResponse, MintRequest, MintResponse, RestoreRequest, RestoreResponse,
- SwapRequest, SwapResponse,
- };
- use crate::wallet::auth::{AuthMintConnector, AuthWallet};
- type Cache = (u64, HashSet<(nut19::Method, nut19::Path)>);
- /// Http Client
- #[derive(Debug, Clone)]
- pub struct HttpClient<T>
- where
- T: Transport + Send + Sync + 'static,
- {
- transport: Arc<T>,
- mint_url: MintUrl,
- cache_support: Arc<StdRwLock<Cache>>,
- auth_wallet: Arc<RwLock<Option<AuthWallet>>>,
- }
- impl<T> HttpClient<T>
- where
- T: Transport + Send + Sync + 'static,
- {
- /// Create new [`HttpClient`] with a provided transport implementation.
- pub fn with_transport(
- mint_url: MintUrl,
- transport: T,
- auth_wallet: Option<AuthWallet>,
- ) -> Self {
- Self {
- transport: transport.into(),
- mint_url,
- auth_wallet: Arc::new(RwLock::new(auth_wallet)),
- cache_support: Default::default(),
- }
- }
- /// Create new [`HttpClient`]
- pub fn new(mint_url: MintUrl, auth_wallet: Option<AuthWallet>) -> Self {
- Self {
- transport: T::default().into(),
- mint_url,
- auth_wallet: Arc::new(RwLock::new(auth_wallet)),
- cache_support: Default::default(),
- }
- }
- /// Get auth token for a protected endpoint
- #[instrument(skip(self))]
- pub async fn get_auth_token(
- &self,
- method: Method,
- path: RoutePath,
- ) -> Result<Option<AuthToken>, Error> {
- let auth_wallet = self.auth_wallet.read().await;
- match auth_wallet.as_ref() {
- Some(auth_wallet) => {
- let endpoint = ProtectedEndpoint::new(method, path);
- auth_wallet.get_auth_for_request(&endpoint).await
- }
- None => Ok(None),
- }
- }
- /// Create new [`HttpClient`] with a proxy for specific TLDs.
- /// Specifying `None` for `host_matcher` will use the proxy for all
- /// requests.
- pub fn with_proxy(
- mint_url: MintUrl,
- proxy: Url,
- host_matcher: Option<&str>,
- accept_invalid_certs: bool,
- ) -> Result<Self, Error> {
- let mut transport = T::default();
- transport.with_proxy(proxy, host_matcher, accept_invalid_certs)?;
- Ok(Self {
- transport: transport.into(),
- mint_url,
- auth_wallet: Arc::new(RwLock::new(None)),
- cache_support: Default::default(),
- })
- }
- /// Generic implementation of a retriable http request
- ///
- /// The retry only happens if the mint supports replay through the Caching of NUT-19.
- #[inline(always)]
- async fn retriable_http_request<P, R>(
- &self,
- method: nut19::Method,
- path: nut19::Path,
- auth_token: Option<AuthToken>,
- custom_headers: &[(&str, &str)],
- payload: &P,
- ) -> Result<R, Error>
- where
- P: Serialize + ?Sized + Send + Sync,
- R: DeserializeOwned,
- {
- let started = Instant::now();
- let retriable_window = self
- .cache_support
- .read()
- .map(|cache_support| {
- cache_support
- .1
- .get(&(method, path.clone()))
- .map(|_| cache_support.0)
- })
- .unwrap_or_default()
- .map(Duration::from_secs)
- .unwrap_or_default();
- let transport = self.transport.clone();
- loop {
- let url = match &path {
- nut19::Path::Swap => self.mint_url.join_paths(&["v1", "swap"])?,
- nut19::Path::Custom(custom_path) => {
- // Custom paths should be in the format "/v1/mint/{method}" or "/v1/melt/{method}"
- // Remove leading slash if present
- let path_str = custom_path.trim_start_matches('/');
- let parts: Vec<&str> = path_str.split('/').collect();
- self.mint_url.join_paths(&parts)?
- }
- };
- let result = match method {
- nut19::Method::Get => {
- transport
- .http_get_with_headers(url, auth_token.clone(), custom_headers)
- .await
- }
- nut19::Method::Post => {
- transport
- .http_post_with_headers(url, auth_token.clone(), custom_headers, payload)
- .await
- }
- };
- if result.is_ok() {
- return result;
- }
- match result.as_ref() {
- Err(Error::HttpError(status_code, _)) => {
- let status_code = status_code.to_owned().unwrap_or_default();
- if (400..=499).contains(&status_code) {
- // 4xx errors won't be 'solved' by retrying
- return result;
- }
- // retry request, if possible
- tracing::error!("Failed http_request {:?}", result.as_ref().err());
- if retriable_window < started.elapsed() {
- return result;
- }
- }
- Err(_) => return result,
- _ => unreachable!(),
- };
- }
- }
- }
- #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
- #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
- impl<T> MintConnector for HttpClient<T>
- where
- T: Transport + Send + Sync + 'static,
- {
- #[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
- #[instrument(skip(self), fields(mint_url = %self.mint_url))]
- async fn resolve_dns_txt(&self, domain: &str) -> Result<Vec<String>, Error> {
- self.transport.resolve_dns_txt(domain).await
- }
- /// Fetch Lightning address pay request data
- #[instrument(skip(self))]
- async fn fetch_lnurl_pay_request(
- &self,
- url: &str,
- ) -> Result<crate::lightning_address::LnurlPayResponse, Error> {
- let parsed_url =
- url::Url::parse(url).map_err(|e| Error::Custom(format!("Invalid URL: {}", e)))?;
- self.transport.http_get(parsed_url, None).await
- }
- /// Fetch invoice from Lightning address callback
- #[instrument(skip(self))]
- async fn fetch_lnurl_invoice(
- &self,
- url: &str,
- ) -> Result<crate::lightning_address::LnurlPayInvoiceResponse, Error> {
- let parsed_url =
- url::Url::parse(url).map_err(|e| Error::Custom(format!("Invalid URL: {}", e)))?;
- self.transport.http_get(parsed_url, None).await
- }
- /// Get Active Mint Keys [NUT-01]
- #[instrument(skip(self), fields(mint_url = %self.mint_url))]
- async fn get_mint_keys(&self) -> Result<Vec<KeySet>, Error> {
- let url = self.mint_url.join_paths(&["v1", "keys"])?;
- let transport = self.transport.clone();
- Ok(transport.http_get::<KeysResponse>(url, None).await?.keysets)
- }
- /// Get Keyset Keys [NUT-01]
- #[instrument(skip(self), fields(mint_url = %self.mint_url))]
- async fn get_mint_keyset(&self, keyset_id: Id) -> Result<KeySet, Error> {
- let url = self
- .mint_url
- .join_paths(&["v1", "keys", &keyset_id.to_string()])?;
- let transport = self.transport.clone();
- let keys_response = transport.http_get::<KeysResponse>(url, None).await?;
- Ok(keys_response
- .keysets
- .first()
- .ok_or(Error::UnknownKeySet)?
- .clone())
- }
- /// Get Keysets [NUT-02]
- #[instrument(skip(self), fields(mint_url = %self.mint_url))]
- async fn get_mint_keysets(&self) -> Result<KeysetResponse, Error> {
- let url = self.mint_url.join_paths(&["v1", "keysets"])?;
- let transport = self.transport.clone();
- transport.http_get(url, None).await
- }
- /// Mint Quote [NUT-04]
- #[instrument(skip(self), fields(mint_url = %self.mint_url))]
- async fn post_mint_quote(
- &self,
- request: MintQuoteBolt11Request,
- ) -> Result<MintQuoteBolt11Response<String>, Error> {
- let url = self
- .mint_url
- .join_paths(&["v1", "mint", "quote", "bolt11"])?;
- let auth_token = self
- .get_auth_token(
- Method::Post,
- RoutePath::MintQuote(PaymentMethod::Known(KnownMethod::Bolt11).to_string()),
- )
- .await?;
- self.transport.http_post(url, auth_token, &request).await
- }
- /// Mint Quote status
- #[instrument(skip(self), fields(mint_url = %self.mint_url))]
- async fn get_mint_quote_status(
- &self,
- quote_id: &str,
- ) -> Result<MintQuoteBolt11Response<String>, Error> {
- let url = self
- .mint_url
- .join_paths(&["v1", "mint", "quote", "bolt11", quote_id])?;
- let auth_token = self
- .get_auth_token(
- Method::Get,
- RoutePath::MintQuote(PaymentMethod::Known(KnownMethod::Bolt11).to_string()),
- )
- .await?;
- self.transport.http_get(url, auth_token).await
- }
- /// Mint Tokens [NUT-04]
- #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
- async fn post_mint(
- &self,
- method: &PaymentMethod,
- request: MintRequest<String>,
- ) -> Result<MintResponse, Error> {
- let auth_token = self
- .get_auth_token(Method::Post, RoutePath::Mint(method.to_string()))
- .await?;
- let path = match method {
- PaymentMethod::Known(KnownMethod::Bolt11) => {
- nut19::Path::Custom("/v1/mint/bolt11".to_string())
- }
- PaymentMethod::Known(KnownMethod::Bolt12) => {
- nut19::Path::Custom("/v1/mint/bolt12".to_string())
- }
- PaymentMethod::Custom(m) => nut19::Path::custom_mint(m),
- };
- self.retriable_http_request(nut19::Method::Post, path, auth_token, &[], &request)
- .await
- }
- /// Melt Quote [NUT-05]
- #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
- async fn post_melt_quote(
- &self,
- request: MeltQuoteBolt11Request,
- ) -> Result<MeltQuoteBolt11Response<String>, Error> {
- let url = self
- .mint_url
- .join_paths(&["v1", "melt", "quote", "bolt11"])?;
- let auth_token = self
- .get_auth_token(
- Method::Post,
- RoutePath::MeltQuote(PaymentMethod::Known(KnownMethod::Bolt11).to_string()),
- )
- .await?;
- self.transport.http_post(url, auth_token, &request).await
- }
- /// Melt Quote Status
- #[instrument(skip(self), fields(mint_url = %self.mint_url))]
- async fn get_melt_quote_status(
- &self,
- quote_id: &str,
- ) -> Result<MeltQuoteBolt11Response<String>, Error> {
- let url = self
- .mint_url
- .join_paths(&["v1", "melt", "quote", "bolt11", quote_id])?;
- let auth_token = self
- .get_auth_token(
- Method::Get,
- RoutePath::MeltQuote(PaymentMethod::Known(KnownMethod::Bolt11).to_string()),
- )
- .await?;
- self.transport.http_get(url, auth_token).await
- }
- /// Melt [NUT-05]
- /// [Nut-08] Lightning fee return if outputs defined
- #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
- async fn post_melt_with_options(
- &self,
- method: &PaymentMethod,
- request: MeltRequest<String>,
- options: MeltOptions,
- ) -> Result<MeltQuoteBolt11Response<String>, Error> {
- let auth_token = self
- .get_auth_token(Method::Post, RoutePath::Melt(method.to_string()))
- .await?;
- let path = match method {
- PaymentMethod::Known(KnownMethod::Bolt11) => {
- nut19::Path::Custom("/v1/melt/bolt11".to_string())
- }
- PaymentMethod::Known(KnownMethod::Bolt12) => {
- nut19::Path::Custom("/v1/melt/bolt12".to_string())
- }
- PaymentMethod::Custom(m) => nut19::Path::custom_melt(m),
- };
- let custom_headers = if options.async_melt {
- vec![("Prefer", "respond-async")]
- } else {
- vec![]
- };
- self.retriable_http_request(
- nut19::Method::Post,
- path,
- auth_token,
- &custom_headers,
- &request,
- )
- .await
- }
- /// Swap Token [NUT-03]
- #[instrument(skip(self, swap_request), fields(mint_url = %self.mint_url))]
- async fn post_swap(&self, swap_request: SwapRequest) -> Result<SwapResponse, Error> {
- let auth_token = self.get_auth_token(Method::Post, RoutePath::Swap).await?;
- self.retriable_http_request(
- nut19::Method::Post,
- nut19::Path::Swap,
- auth_token,
- &[],
- &swap_request,
- )
- .await
- }
- /// Helper to get mint info
- async fn get_mint_info(&self) -> Result<MintInfo, Error> {
- let url = self.mint_url.join_paths(&["v1", "info"])?;
- let transport = self.transport.clone();
- let info: MintInfo = transport.http_get(url, None).await?;
- if let Ok(mut cache_support) = self.cache_support.write() {
- *cache_support = (
- info.nuts.nut19.ttl.unwrap_or(300),
- info.nuts
- .nut19
- .cached_endpoints
- .clone()
- .into_iter()
- .map(|cached_endpoint| (cached_endpoint.method, cached_endpoint.path))
- .collect(),
- );
- }
- Ok(info)
- }
- async fn get_auth_wallet(&self) -> Option<AuthWallet> {
- self.auth_wallet.read().await.clone()
- }
- async fn set_auth_wallet(&self, wallet: Option<AuthWallet>) {
- *self.auth_wallet.write().await = wallet;
- }
- /// Spendable check [NUT-07]
- #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
- async fn post_check_state(
- &self,
- request: CheckStateRequest,
- ) -> Result<CheckStateResponse, Error> {
- let url = self.mint_url.join_paths(&["v1", "checkstate"])?;
- let auth_token = self
- .get_auth_token(Method::Post, RoutePath::Checkstate)
- .await?;
- self.transport.http_post(url, auth_token, &request).await
- }
- /// Restore request [NUT-13]
- #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
- async fn post_restore(&self, request: RestoreRequest) -> Result<RestoreResponse, Error> {
- let url = self.mint_url.join_paths(&["v1", "restore"])?;
- let auth_token = self
- .get_auth_token(Method::Post, RoutePath::Restore)
- .await?;
- self.transport.http_post(url, auth_token, &request).await
- }
- /// Mint Quote Bolt12 [NUT-23]
- #[instrument(skip(self), fields(mint_url = %self.mint_url))]
- async fn post_mint_bolt12_quote(
- &self,
- request: MintQuoteBolt12Request,
- ) -> Result<MintQuoteBolt12Response<String>, Error> {
- let url = self
- .mint_url
- .join_paths(&["v1", "mint", "quote", "bolt12"])?;
- let auth_token = self
- .get_auth_token(
- Method::Post,
- RoutePath::MintQuote(PaymentMethod::Known(KnownMethod::Bolt12).to_string()),
- )
- .await?;
- self.transport.http_post(url, auth_token, &request).await
- }
- /// Mint Quote Bolt12 status
- #[instrument(skip(self), fields(mint_url = %self.mint_url))]
- async fn get_mint_quote_bolt12_status(
- &self,
- quote_id: &str,
- ) -> Result<MintQuoteBolt12Response<String>, Error> {
- let url = self
- .mint_url
- .join_paths(&["v1", "mint", "quote", "bolt12", quote_id])?;
- let auth_token = self
- .get_auth_token(
- Method::Get,
- RoutePath::MintQuote(PaymentMethod::Known(KnownMethod::Bolt12).to_string()),
- )
- .await?;
- self.transport.http_get(url, auth_token).await
- }
- /// Melt Quote Bolt12 [NUT-23]
- #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
- async fn post_melt_bolt12_quote(
- &self,
- request: MeltQuoteBolt12Request,
- ) -> Result<MeltQuoteBolt12Response<String>, Error> {
- let url = self
- .mint_url
- .join_paths(&["v1", "melt", "quote", "bolt12"])?;
- let auth_token = self
- .get_auth_token(
- Method::Post,
- RoutePath::MeltQuote(PaymentMethod::Known(KnownMethod::Bolt12).to_string()),
- )
- .await?;
- self.transport.http_post(url, auth_token, &request).await
- }
- /// Melt Quote Bolt12 Status [NUT-23]
- #[instrument(skip(self), fields(mint_url = %self.mint_url))]
- async fn get_melt_bolt12_quote_status(
- &self,
- quote_id: &str,
- ) -> Result<MeltQuoteBolt12Response<String>, Error> {
- let url = self
- .mint_url
- .join_paths(&["v1", "melt", "quote", "bolt12", quote_id])?;
- let auth_token = self
- .get_auth_token(
- Method::Get,
- RoutePath::MeltQuote(PaymentMethod::Known(KnownMethod::Bolt12).to_string()),
- )
- .await?;
- self.transport.http_get(url, auth_token).await
- }
- /// Mint Quote for Custom Payment Method
- #[instrument(skip(self), fields(mint_url = %self.mint_url))]
- async fn post_mint_custom_quote(
- &self,
- method: &PaymentMethod,
- request: MintQuoteCustomRequest,
- ) -> Result<MintQuoteCustomResponse<String>, Error> {
- let url = self
- .mint_url
- .join_paths(&["v1", "mint", "quote", &method.to_string()])?;
- let auth_token = self
- .get_auth_token(Method::Post, RoutePath::MintQuote(method.to_string()))
- .await?;
- self.transport.http_post(url, auth_token, &request).await
- }
- /// Mint Quote Status for Custom Payment Method
- #[instrument(skip(self), fields(mint_url = %self.mint_url))]
- async fn get_mint_quote_custom_status(
- &self,
- method: &str,
- quote_id: &str,
- ) -> Result<MintQuoteCustomResponse<String>, Error> {
- let url = self
- .mint_url
- .join_paths(&["v1", "mint", "quote", method, quote_id])?;
- let auth_token = self
- .get_auth_token(Method::Get, RoutePath::MintQuote(method.to_string()))
- .await?;
- self.transport.http_get(url, auth_token).await
- }
- /// Melt Quote for Custom Payment Method
- #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
- async fn post_melt_custom_quote(
- &self,
- request: MeltQuoteCustomRequest,
- ) -> Result<MeltQuoteCustomResponse<String>, Error> {
- let url = self
- .mint_url
- .join_paths(&["v1", "melt", "quote", &request.method])?;
- let auth_token = self
- .get_auth_token(Method::Post, RoutePath::MeltQuote(request.method.clone()))
- .await?;
- self.transport.http_post(url, auth_token, &request).await
- }
- /// Melt Quote Status for Custom Payment Method
- #[instrument(skip(self), fields(mint_url = %self.mint_url))]
- async fn get_melt_quote_custom_status(
- &self,
- method: &str,
- quote_id: &str,
- ) -> Result<MeltQuoteCustomResponse<String>, Error> {
- let url = self
- .mint_url
- .join_paths(&["v1", "melt", "quote", method, quote_id])?;
- let auth_token = self
- .get_auth_token(Method::Get, RoutePath::MeltQuote(method.to_string()))
- .await?;
- self.transport.http_get(url, auth_token).await
- }
- }
- /// Http Client
- #[derive(Debug, Clone)]
- pub struct AuthHttpClient<T>
- where
- T: Transport + Send + Sync + 'static,
- {
- transport: Arc<T>,
- mint_url: MintUrl,
- cat: Arc<RwLock<AuthToken>>,
- }
- impl<T> AuthHttpClient<T>
- where
- T: Transport + Send + Sync + 'static,
- {
- /// Create new [`AuthHttpClient`]
- pub fn new(mint_url: MintUrl, cat: Option<AuthToken>) -> Self {
- Self {
- transport: T::default().into(),
- mint_url,
- cat: Arc::new(RwLock::new(
- cat.unwrap_or(AuthToken::ClearAuth("".to_string())),
- )),
- }
- }
- }
- #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
- #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
- impl<T> AuthMintConnector for AuthHttpClient<T>
- where
- T: Transport + Send + Sync + 'static,
- {
- async fn get_auth_token(&self) -> Result<AuthToken, Error> {
- Ok(self.cat.read().await.clone())
- }
- async fn set_auth_token(&self, token: AuthToken) -> Result<(), Error> {
- *self.cat.write().await = token;
- Ok(())
- }
- /// 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?;
- Ok(mint_info)
- }
- /// Get Auth Keyset Keys [NUT-22]
- #[instrument(skip(self), fields(mint_url = %self.mint_url))]
- async fn get_mint_blind_auth_keyset(&self, keyset_id: Id) -> Result<KeySet, Error> {
- let url =
- 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 keyset = keys_response
- .keysets
- .drain(0..1)
- .next()
- .ok_or_else(|| Error::UnknownKeySet)?;
- Ok(keyset)
- }
- /// Get Auth Keysets [NUT-22]
- #[instrument(skip(self), fields(mint_url = %self.mint_url))]
- async fn get_mint_blind_auth_keysets(&self) -> Result<KeysetResponse, Error> {
- let url = self
- .mint_url
- .join_paths(&["v1", "auth", "blind", "keysets"])?;
- self.transport.http_get(url, None).await
- }
- /// Mint Tokens [NUT-22]
- #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
- async fn post_mint_blind_auth(&self, request: MintAuthRequest) -> Result<MintResponse, Error> {
- let url = self.mint_url.join_paths(&["v1", "auth", "blind", "mint"])?;
- self.transport
- .http_post(url, Some(self.cat.read().await.clone()), &request)
- .await
- }
- }
|