breez_sdk_spark/sdk/lnurl/
mod.rs1use breez_sdk_common::lnurl::{self, error::LnurlError};
2
3use crate::{
4 BuildUnsignedLnurlPayPackageRequest, LnurlAuthRequestDetails, LnurlCallbackStatus,
5 LnurlPayRequest, LnurlPayResponse, LnurlWithdrawInfo, LnurlWithdrawRequest,
6 LnurlWithdrawResponse, PrepareLnurlPayRequest, PrepareLnurlPayResponse,
7 PublishSignedLnurlPayPackageRequest, PublishSignedLnurlPayResponse, UnsignedTransferPackage,
8 WaitForPaymentIdentifier,
9 error::SdkError,
10 persist::{ObjectCacheRepository, PaymentMetadata},
11};
12use breez_sdk_common::lnurl::withdraw::execute_lnurl_withdraw;
13
14use super::BreezSdk;
15
16mod pay;
17
18#[cfg_attr(feature = "uniffi", uniffi::export(async_runtime = "tokio"))]
19#[allow(clippy::needless_pass_by_value)]
20impl BreezSdk {
21 pub async fn prepare_lnurl_pay(
22 &self,
23 request: PrepareLnurlPayRequest,
24 ) -> Result<PrepareLnurlPayResponse, SdkError> {
25 pay::prepare(self, request).await
26 }
27
28 pub async fn lnurl_pay(&self, request: LnurlPayRequest) -> Result<LnurlPayResponse, SdkError> {
29 pay::send(self, request).await
30 }
31
32 pub async fn build_unsigned_lnurl_pay_package(
33 &self,
34 request: BuildUnsignedLnurlPayPackageRequest,
35 ) -> Result<UnsignedTransferPackage, SdkError> {
36 pay::build_package(self, &request.prepare_response).await
37 }
38
39 pub async fn publish_signed_lnurl_pay_package(
40 &self,
41 request: PublishSignedLnurlPayPackageRequest,
42 ) -> Result<PublishSignedLnurlPayResponse, SdkError> {
43 self.maybe_ensure_spark_private_mode_initialized().await?;
44 pay::publish_signed_package(self, request.signed_package).await
45 }
46
47 pub async fn lnurl_withdraw(
73 &self,
74 request: LnurlWithdrawRequest,
75 ) -> Result<LnurlWithdrawResponse, SdkError> {
76 self.maybe_ensure_spark_private_mode_initialized().await?;
77 let LnurlWithdrawRequest {
78 amount_sats,
79 withdraw_request,
80 completion_timeout_secs,
81 } = request;
82 let withdraw_request: breez_sdk_common::lnurl::withdraw::LnurlWithdrawRequestDetails =
83 withdraw_request.into();
84 if !withdraw_request.is_amount_valid(amount_sats) {
85 return Err(SdkError::InvalidInput(
86 "Amount must be within min/max LNURL withdrawable limits".to_string(),
87 ));
88 }
89
90 let receive = self
93 .receive_bolt11_invoice_inner(
94 withdraw_request.default_description.clone(),
95 Some(amount_sats),
96 None,
97 None,
98 None,
99 )
100 .await?;
101 let payment_request = receive.invoice.clone();
102 let ssp_receive_id = receive.id;
103
104 let cache = ObjectCacheRepository::new(self.storage.clone());
106 cache
107 .save_payment_metadata(
108 &payment_request,
109 &PaymentMetadata {
110 lnurl_withdraw_info: Some(LnurlWithdrawInfo {
111 withdraw_url: withdraw_request.callback.clone(),
112 }),
113 lnurl_description: Some(withdraw_request.default_description.clone()),
114 ..Default::default()
115 },
116 )
117 .await?;
118
119 let withdraw_response = execute_lnurl_withdraw(
121 self.lnurl_client.as_ref(),
122 &withdraw_request,
123 &payment_request,
124 )
125 .await?;
126 if let lnurl::withdraw::ValidatedCallbackResponse::EndpointError { data } =
127 withdraw_response
128 {
129 return Err(LnurlError::EndpointError(data.reason).into());
130 }
131
132 let completion_timeout_secs = match completion_timeout_secs {
133 Some(secs) if secs > 0 => secs,
134 _ => {
135 return Ok(LnurlWithdrawResponse {
136 payment_request,
137 payment: None,
138 });
139 }
140 };
141
142 let payment = self
144 .wait_for_incoming_payment(
145 WaitForPaymentIdentifier::LightningReceive {
146 invoice: payment_request.clone(),
147 ssp_id: ssp_receive_id,
148 },
149 completion_timeout_secs,
150 )
151 .await
152 .ok();
153 Ok(LnurlWithdrawResponse {
154 payment_request,
155 payment,
156 })
157 }
158
159 pub async fn lnurl_auth(
165 &self,
166 request_data: LnurlAuthRequestDetails,
167 ) -> Result<LnurlCallbackStatus, SdkError> {
168 let Some(lnurl_auth_signer) = self.lnurl_auth_signer.as_ref() else {
170 return Err(SdkError::Generic(
171 "LNURL-auth requires a signer that supports HMAC".to_string(),
172 ));
173 };
174 let request: breez_sdk_common::lnurl::auth::LnurlAuthRequestDetails = request_data.into();
175 let status = breez_sdk_common::lnurl::auth::perform_lnurl_auth(
176 self.lnurl_client.as_ref(),
177 &request,
178 lnurl_auth_signer.as_ref(),
179 )
180 .await
181 .map_err(|e| match e {
182 LnurlError::ServiceConnectivity(msg) => SdkError::NetworkError(msg.to_string()),
183 LnurlError::InvalidUri(msg) => SdkError::InvalidInput(msg),
184 _ => SdkError::Generic(e.to_string()),
185 })?;
186 Ok(status.into())
187 }
188}