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#[macros::instance_span]
19#[cfg_attr(feature = "uniffi", uniffi::export(async_runtime = "tokio"))]
20#[allow(clippy::needless_pass_by_value)]
21impl BreezSdk {
22 pub async fn prepare_lnurl_pay(
23 &self,
24 request: PrepareLnurlPayRequest,
25 ) -> Result<PrepareLnurlPayResponse, SdkError> {
26 pay::prepare(self, request).await
27 }
28
29 pub async fn lnurl_pay(&self, request: LnurlPayRequest) -> Result<LnurlPayResponse, SdkError> {
30 pay::send(self, request).await
31 }
32
33 pub async fn build_unsigned_lnurl_pay_package(
34 &self,
35 request: BuildUnsignedLnurlPayPackageRequest,
36 ) -> Result<UnsignedTransferPackage, SdkError> {
37 pay::build_package(self, &request.prepare_response).await
38 }
39
40 pub async fn publish_signed_lnurl_pay_package(
41 &self,
42 request: PublishSignedLnurlPayPackageRequest,
43 ) -> Result<PublishSignedLnurlPayResponse, SdkError> {
44 self.maybe_ensure_spark_private_mode_initialized().await?;
45 pay::publish_signed_package(self, request.signed_package).await
46 }
47
48 pub async fn lnurl_withdraw(
74 &self,
75 request: LnurlWithdrawRequest,
76 ) -> Result<LnurlWithdrawResponse, SdkError> {
77 self.maybe_ensure_spark_private_mode_initialized().await?;
78 let LnurlWithdrawRequest {
79 amount_sats,
80 withdraw_request,
81 completion_timeout_secs,
82 } = request;
83 let withdraw_request: breez_sdk_common::lnurl::withdraw::LnurlWithdrawRequestDetails =
84 withdraw_request.into();
85 if !withdraw_request.is_amount_valid(amount_sats) {
86 return Err(SdkError::InvalidInput(
87 "Amount must be within min/max LNURL withdrawable limits".to_string(),
88 ));
89 }
90
91 lnurl::security::validate_callback_url(
95 &withdraw_request.callback,
96 lnurl::security::callback_trust(&withdraw_request.url),
97 )?;
98
99 let receive = self
102 .receive_bolt11_invoice_inner(
103 withdraw_request.default_description.clone(),
104 Some(amount_sats),
105 None,
106 None,
107 None,
108 None,
109 )
110 .await?;
111 let payment_request = receive.invoice.clone();
112 let ssp_receive_id = receive.id;
113
114 let cache = ObjectCacheRepository::new(self.storage.clone());
116 cache
117 .save_payment_metadata(
118 &payment_request,
119 &PaymentMetadata {
120 lnurl_withdraw_info: Some(LnurlWithdrawInfo {
121 withdraw_url: withdraw_request.callback.clone(),
122 }),
123 lnurl_description: Some(withdraw_request.default_description.clone()),
124 ..Default::default()
125 },
126 )
127 .await?;
128
129 let withdraw_response = execute_lnurl_withdraw(
131 self.lnurl_client.as_ref(),
132 &withdraw_request,
133 &payment_request,
134 self.config.proxy.is_none(),
137 )
138 .await?;
139 if let lnurl::withdraw::ValidatedCallbackResponse::EndpointError { data } =
140 withdraw_response
141 {
142 return Err(LnurlError::EndpointError(data.reason).into());
143 }
144
145 let completion_timeout_secs = match completion_timeout_secs {
146 Some(secs) if secs > 0 => secs,
147 _ => {
148 return Ok(LnurlWithdrawResponse {
149 payment_request,
150 payment: None,
151 });
152 }
153 };
154
155 let payment = self
157 .wait_for_incoming_payment(
158 WaitForPaymentIdentifier::LightningReceive {
159 invoice: payment_request.clone(),
160 ssp_id: ssp_receive_id,
161 },
162 completion_timeout_secs,
163 )
164 .await
165 .ok();
166 Ok(LnurlWithdrawResponse {
167 payment_request,
168 payment,
169 })
170 }
171
172 pub async fn lnurl_auth(
178 &self,
179 request_data: LnurlAuthRequestDetails,
180 ) -> Result<LnurlCallbackStatus, SdkError> {
181 let Some(lnurl_auth_signer) = self.lnurl_auth_signer.as_ref() else {
183 return Err(SdkError::Generic(
184 "LNURL-auth requires a signer that supports HMAC".to_string(),
185 ));
186 };
187 let request: breez_sdk_common::lnurl::auth::LnurlAuthRequestDetails = request_data.into();
188 let status = breez_sdk_common::lnurl::auth::perform_lnurl_auth(
189 self.lnurl_client.as_ref(),
190 &request,
191 lnurl_auth_signer.as_ref(),
192 )
193 .await
194 .map_err(|e| match e {
195 LnurlError::ServiceConnectivity(msg) => SdkError::NetworkError(msg.to_string()),
196 LnurlError::InvalidUri(msg) => SdkError::InvalidInput(msg),
197 _ => SdkError::Generic(e.to_string()),
198 })?;
199 Ok(status.into())
200 }
201}