bolt12_router.rs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. use anyhow::Result;
  2. use axum::extract::{Json, Path, State};
  3. use axum::response::Response;
  4. #[cfg(feature = "swagger")]
  5. use cdk::error::ErrorResponse;
  6. use cdk::mint::QuoteId;
  7. #[cfg(feature = "auth")]
  8. use cdk::nuts::nut21::{Method, ProtectedEndpoint, RoutePath};
  9. use cdk::nuts::{
  10. MeltQuoteBolt11Response, MeltQuoteBolt12Request, MeltRequest, MintQuoteBolt12Request,
  11. MintQuoteBolt12Response, MintRequest, MintResponse,
  12. };
  13. use paste::paste;
  14. use tracing::instrument;
  15. #[cfg(feature = "auth")]
  16. use crate::auth::AuthHeader;
  17. use crate::{into_response, post_cache_wrapper, MintState};
  18. post_cache_wrapper!(post_mint_bolt12, MintRequest<QuoteId>, MintResponse);
  19. post_cache_wrapper!(
  20. post_melt_bolt12,
  21. MeltRequest<QuoteId>,
  22. MeltQuoteBolt11Response<QuoteId>
  23. );
  24. #[cfg_attr(feature = "swagger", utoipa::path(
  25. get,
  26. context_path = "/v1",
  27. path = "/mint/quote/bolt12",
  28. responses(
  29. (status = 200, description = "Successful response", body = MintQuoteBolt12Response<String>, content_type = "application/json")
  30. )
  31. ))]
  32. /// Get mint bolt12 quote
  33. #[instrument(skip_all, fields(amount = ?payload.amount))]
  34. pub async fn post_mint_bolt12_quote(
  35. #[cfg(feature = "auth")] auth: AuthHeader,
  36. State(state): State<MintState>,
  37. Json(payload): Json<MintQuoteBolt12Request>,
  38. ) -> Result<Json<MintQuoteBolt12Response<QuoteId>>, Response> {
  39. #[cfg(feature = "auth")]
  40. {
  41. state
  42. .mint
  43. .verify_auth(
  44. auth.into(),
  45. &ProtectedEndpoint::new(Method::Post, RoutePath::MintQuoteBolt12),
  46. )
  47. .await
  48. .map_err(into_response)?;
  49. }
  50. let quote = state
  51. .mint
  52. .get_mint_quote(payload.into())
  53. .await
  54. .map_err(into_response)?;
  55. Ok(Json(quote.try_into().map_err(into_response)?))
  56. }
  57. #[cfg_attr(feature = "swagger", utoipa::path(
  58. get,
  59. context_path = "/v1",
  60. path = "/mint/quote/bolt12/{quote_id}",
  61. params(
  62. ("quote_id" = String, description = "The quote ID"),
  63. ),
  64. responses(
  65. (status = 200, description = "Successful response", body = MintQuoteBolt12Response<String>, content_type = "application/json"),
  66. (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
  67. )
  68. ))]
  69. /// Get mint bolt12 quote
  70. #[instrument(skip_all, fields(quote_id = ?quote_id))]
  71. pub async fn get_check_mint_bolt12_quote(
  72. #[cfg(feature = "auth")] auth: AuthHeader,
  73. State(state): State<MintState>,
  74. Path(quote_id): Path<QuoteId>,
  75. ) -> Result<Json<MintQuoteBolt12Response<QuoteId>>, Response> {
  76. #[cfg(feature = "auth")]
  77. {
  78. state
  79. .mint
  80. .verify_auth(
  81. auth.into(),
  82. &ProtectedEndpoint::new(Method::Get, RoutePath::MintQuoteBolt12),
  83. )
  84. .await
  85. .map_err(into_response)?;
  86. }
  87. let quote = state
  88. .mint
  89. .check_mint_quote(&quote_id)
  90. .await
  91. .map_err(into_response)?;
  92. Ok(Json(quote.try_into().map_err(into_response)?))
  93. }
  94. #[cfg_attr(feature = "swagger", utoipa::path(
  95. post,
  96. context_path = "/v1",
  97. path = "/mint/bolt12",
  98. request_body(content = MintRequest<String>, description = "Request params", content_type = "application/json"),
  99. responses(
  100. (status = 200, description = "Successful response", body = MintResponse, content_type = "application/json"),
  101. (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
  102. )
  103. ))]
  104. /// Request a quote for melting tokens
  105. #[instrument(skip_all, fields(quote_id = ?payload.quote))]
  106. pub async fn post_mint_bolt12(
  107. #[cfg(feature = "auth")] auth: AuthHeader,
  108. State(state): State<MintState>,
  109. Json(payload): Json<MintRequest<QuoteId>>,
  110. ) -> Result<Json<MintResponse>, Response> {
  111. #[cfg(feature = "auth")]
  112. {
  113. state
  114. .mint
  115. .verify_auth(
  116. auth.into(),
  117. &ProtectedEndpoint::new(Method::Post, RoutePath::MintBolt12),
  118. )
  119. .await
  120. .map_err(into_response)?;
  121. }
  122. let res = state
  123. .mint
  124. .process_mint_request(payload)
  125. .await
  126. .map_err(|err| {
  127. tracing::error!("Could not process mint: {}", err);
  128. into_response(err)
  129. })?;
  130. Ok(Json(res))
  131. }
  132. #[cfg_attr(feature = "swagger", utoipa::path(
  133. post,
  134. context_path = "/v1",
  135. path = "/melt/quote/bolt12",
  136. request_body(content = MeltQuoteBolt12Request, description = "Quote params", content_type = "application/json"),
  137. responses(
  138. (status = 200, description = "Successful response", body = MeltQuoteBolt11Response<String>, content_type = "application/json"),
  139. (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
  140. )
  141. ))]
  142. pub async fn post_melt_bolt12_quote(
  143. #[cfg(feature = "auth")] auth: AuthHeader,
  144. State(state): State<MintState>,
  145. Json(payload): Json<MeltQuoteBolt12Request>,
  146. ) -> Result<Json<MeltQuoteBolt11Response<QuoteId>>, Response> {
  147. #[cfg(feature = "auth")]
  148. {
  149. state
  150. .mint
  151. .verify_auth(
  152. auth.into(),
  153. &ProtectedEndpoint::new(Method::Post, RoutePath::MeltQuoteBolt12),
  154. )
  155. .await
  156. .map_err(into_response)?;
  157. }
  158. let quote = state
  159. .mint
  160. .get_melt_quote(payload.into())
  161. .await
  162. .map_err(into_response)?;
  163. Ok(Json(quote))
  164. }
  165. #[cfg_attr(feature = "swagger", utoipa::path(
  166. post,
  167. context_path = "/v1",
  168. path = "/melt/bolt12",
  169. request_body(content = MeltRequest<String>, description = "Melt params", content_type = "application/json"),
  170. responses(
  171. (status = 200, description = "Successful response", body = MeltQuoteBolt11Response<String>, content_type = "application/json"),
  172. (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
  173. )
  174. ))]
  175. /// Melt tokens for a Bitcoin payment that the mint will make for the user in exchange
  176. ///
  177. /// Requests tokens to be destroyed and sent out via Lightning.
  178. pub async fn post_melt_bolt12(
  179. #[cfg(feature = "auth")] auth: AuthHeader,
  180. State(state): State<MintState>,
  181. Json(payload): Json<MeltRequest<QuoteId>>,
  182. ) -> Result<Json<MeltQuoteBolt11Response<QuoteId>>, Response> {
  183. #[cfg(feature = "auth")]
  184. {
  185. state
  186. .mint
  187. .verify_auth(
  188. auth.into(),
  189. &ProtectedEndpoint::new(Method::Post, RoutePath::MeltBolt12),
  190. )
  191. .await
  192. .map_err(into_response)?;
  193. }
  194. let res = state.mint.melt(&payload).await.map_err(into_response)?;
  195. Ok(Json(res))
  196. }