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 lnurl::security::validate_callback_url(
94 &withdraw_request.callback,
95 lnurl::security::callback_trust(&withdraw_request.url),
96 )?;
97
98 let receive = self
101 .receive_bolt11_invoice_inner(
102 withdraw_request.default_description.clone(),
103 Some(amount_sats),
104 None,
105 None,
106 None,
107 )
108 .await?;
109 let payment_request = receive.invoice.clone();
110 let ssp_receive_id = receive.id;
111
112 let cache = ObjectCacheRepository::new(self.storage.clone());
114 cache
115 .save_payment_metadata(
116 &payment_request,
117 &PaymentMetadata {
118 lnurl_withdraw_info: Some(LnurlWithdrawInfo {
119 withdraw_url: withdraw_request.callback.clone(),
120 }),
121 lnurl_description: Some(withdraw_request.default_description.clone()),
122 ..Default::default()
123 },
124 )
125 .await?;
126
127 let withdraw_response = execute_lnurl_withdraw(
129 self.lnurl_client.as_ref(),
130 &withdraw_request,
131 &payment_request,
132 self.config.proxy.is_none(),
135 )
136 .await?;
137 if let lnurl::withdraw::ValidatedCallbackResponse::EndpointError { data } =
138 withdraw_response
139 {
140 return Err(LnurlError::EndpointError(data.reason).into());
141 }
142
143 let completion_timeout_secs = match completion_timeout_secs {
144 Some(secs) if secs > 0 => secs,
145 _ => {
146 return Ok(LnurlWithdrawResponse {
147 payment_request,
148 payment: None,
149 });
150 }
151 };
152
153 let payment = self
155 .wait_for_incoming_payment(
156 WaitForPaymentIdentifier::LightningReceive {
157 invoice: payment_request.clone(),
158 ssp_id: ssp_receive_id,
159 },
160 completion_timeout_secs,
161 )
162 .await
163 .ok();
164 Ok(LnurlWithdrawResponse {
165 payment_request,
166 payment,
167 })
168 }
169
170 pub async fn lnurl_auth(
176 &self,
177 request_data: LnurlAuthRequestDetails,
178 ) -> Result<LnurlCallbackStatus, SdkError> {
179 let Some(lnurl_auth_signer) = self.lnurl_auth_signer.as_ref() else {
181 return Err(SdkError::Generic(
182 "LNURL-auth requires a signer that supports HMAC".to_string(),
183 ));
184 };
185 let request: breez_sdk_common::lnurl::auth::LnurlAuthRequestDetails = request_data.into();
186 let status = breez_sdk_common::lnurl::auth::perform_lnurl_auth(
187 self.lnurl_client.as_ref(),
188 &request,
189 lnurl_auth_signer.as_ref(),
190 )
191 .await
192 .map_err(|e| match e {
193 LnurlError::ServiceConnectivity(msg) => SdkError::NetworkError(msg.to_string()),
194 LnurlError::InvalidUri(msg) => SdkError::InvalidInput(msg),
195 _ => SdkError::Generic(e.to_string()),
196 })?;
197 Ok(status.into())
198 }
199}