mirror of
https://github.com/aljazceru/cdk.git
synced 2026-02-04 04:36:21 +01:00
Add NUT-19 support in the wallet (#912)
* Add NUT-19 support in the wallet
This commit is contained in:
@@ -32,7 +32,7 @@ impl CachedEndpoint {
|
||||
}
|
||||
|
||||
/// HTTP method
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
|
||||
pub enum Method {
|
||||
@@ -43,7 +43,7 @@ pub enum Method {
|
||||
}
|
||||
|
||||
/// Route path
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
|
||||
pub enum Path {
|
||||
/// Bolt11 Mint
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#[cfg(feature = "auth")]
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::{Arc, RwLock as StdRwLock};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use cdk_common::{MeltQuoteBolt12Request, MintQuoteBolt12Request, MintQuoteBolt12Response};
|
||||
use cdk_common::{nut19, MeltQuoteBolt12Request, MintQuoteBolt12Request, MintQuoteBolt12Response};
|
||||
#[cfg(feature = "auth")]
|
||||
use cdk_common::{Method, ProtectedEndpoint, RoutePath};
|
||||
use reqwest::{Client, IntoUrl};
|
||||
@@ -109,11 +110,14 @@ impl HttpClientCore {
|
||||
}
|
||||
}
|
||||
|
||||
type Cache = (u64, HashSet<(nut19::Method, nut19::Path)>);
|
||||
|
||||
/// Http Client
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HttpClient {
|
||||
core: HttpClientCore,
|
||||
mint_url: MintUrl,
|
||||
cache_support: Arc<StdRwLock<Cache>>,
|
||||
#[cfg(feature = "auth")]
|
||||
auth_wallet: Arc<RwLock<Option<AuthWallet>>>,
|
||||
}
|
||||
@@ -126,6 +130,7 @@ impl HttpClient {
|
||||
core: HttpClientCore::new(),
|
||||
mint_url,
|
||||
auth_wallet: Arc::new(RwLock::new(auth_wallet)),
|
||||
cache_support: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +139,7 @@ impl HttpClient {
|
||||
pub fn new(mint_url: MintUrl) -> Self {
|
||||
Self {
|
||||
core: HttpClientCore::new(),
|
||||
cache_support: Default::default(),
|
||||
mint_url,
|
||||
}
|
||||
}
|
||||
@@ -190,8 +196,72 @@ impl HttpClient {
|
||||
mint_url,
|
||||
#[cfg(feature = "auth")]
|
||||
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>,
|
||||
payload: &P,
|
||||
) -> Result<R, Error>
|
||||
where
|
||||
P: Serialize + ?Sized,
|
||||
R: DeserializeOwned,
|
||||
{
|
||||
let started = Instant::now();
|
||||
|
||||
let retriable_window = self
|
||||
.cache_support
|
||||
.read()
|
||||
.map(|cache_support| {
|
||||
cache_support
|
||||
.1
|
||||
.get(&(method, path))
|
||||
.map(|_| cache_support.0)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.map(Duration::from_secs)
|
||||
.unwrap_or_default();
|
||||
|
||||
loop {
|
||||
let url = self.mint_url.join_paths(&match path {
|
||||
nut19::Path::MintBolt11 => vec!["v1", "mint", "bolt11"],
|
||||
nut19::Path::MeltBolt11 => vec!["v1", "melt", "bolt11"],
|
||||
nut19::Path::MintBolt12 => vec!["v1", "mint", "bolt12"],
|
||||
nut19::Path::MeltBolt12 => vec!["v1", "melt", "bolt12"],
|
||||
nut19::Path::Swap => vec!["v1", "swap"],
|
||||
})?;
|
||||
|
||||
let result = match method {
|
||||
nut19::Method::Get => self.core.http_get(url, auth_token.clone()).await,
|
||||
nut19::Method::Post => self.core.http_post(url, auth_token.clone(), payload).await,
|
||||
};
|
||||
|
||||
if result.is_ok() {
|
||||
return result;
|
||||
}
|
||||
|
||||
match result.as_ref() {
|
||||
Err(Error::Database(_) | Error::HttpError(_) | Error::Custom(_)) => {
|
||||
// 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))]
|
||||
@@ -272,7 +342,6 @@ impl MintConnector for HttpClient {
|
||||
/// Mint Tokens [NUT-04]
|
||||
#[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
|
||||
async fn post_mint(&self, request: MintRequest<String>) -> Result<MintResponse, Error> {
|
||||
let url = self.mint_url.join_paths(&["v1", "mint", "bolt11"])?;
|
||||
#[cfg(feature = "auth")]
|
||||
let auth_token = self
|
||||
.get_auth_token(Method::Post, RoutePath::MintBolt11)
|
||||
@@ -280,7 +349,13 @@ impl MintConnector for HttpClient {
|
||||
|
||||
#[cfg(not(feature = "auth"))]
|
||||
let auth_token = None;
|
||||
self.core.http_post(url, auth_token, &request).await
|
||||
self.retriable_http_request(
|
||||
nut19::Method::Post,
|
||||
nut19::Path::MintBolt11,
|
||||
auth_token,
|
||||
&request,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Melt Quote [NUT-05]
|
||||
@@ -329,7 +404,6 @@ impl MintConnector for HttpClient {
|
||||
&self,
|
||||
request: MeltRequest<String>,
|
||||
) -> Result<MeltQuoteBolt11Response<String>, Error> {
|
||||
let url = self.mint_url.join_paths(&["v1", "melt", "bolt11"])?;
|
||||
#[cfg(feature = "auth")]
|
||||
let auth_token = self
|
||||
.get_auth_token(Method::Post, RoutePath::MeltBolt11)
|
||||
@@ -337,25 +411,53 @@ impl MintConnector for HttpClient {
|
||||
|
||||
#[cfg(not(feature = "auth"))]
|
||||
let auth_token = None;
|
||||
self.core.http_post(url, auth_token, &request).await
|
||||
|
||||
self.retriable_http_request(
|
||||
nut19::Method::Post,
|
||||
nut19::Path::MeltBolt11,
|
||||
auth_token,
|
||||
&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 url = self.mint_url.join_paths(&["v1", "swap"])?;
|
||||
#[cfg(feature = "auth")]
|
||||
let auth_token = self.get_auth_token(Method::Post, RoutePath::Swap).await?;
|
||||
|
||||
#[cfg(not(feature = "auth"))]
|
||||
let auth_token = None;
|
||||
self.core.http_post(url, auth_token, &swap_request).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"])?;
|
||||
self.core.http_get(url, None).await
|
||||
let info: MintInfo = self.core.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)
|
||||
}
|
||||
|
||||
#[cfg(feature = "auth")]
|
||||
@@ -485,7 +587,6 @@ impl MintConnector for HttpClient {
|
||||
&self,
|
||||
request: MeltRequest<String>,
|
||||
) -> Result<MeltQuoteBolt11Response<String>, Error> {
|
||||
let url = self.mint_url.join_paths(&["v1", "melt", "bolt12"])?;
|
||||
#[cfg(feature = "auth")]
|
||||
let auth_token = self
|
||||
.get_auth_token(Method::Post, RoutePath::MeltBolt12)
|
||||
@@ -493,7 +594,13 @@ impl MintConnector for HttpClient {
|
||||
|
||||
#[cfg(not(feature = "auth"))]
|
||||
let auth_token = None;
|
||||
self.core.http_post(url, auth_token, &request).await
|
||||
self.retriable_http_request(
|
||||
nut19::Method::Post,
|
||||
nut19::Path::MeltBolt12,
|
||||
auth_token,
|
||||
&request,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user