breez_sdk_spark/sdk/payments/
mod.rs1use spark_wallet::LightningReceivePayment;
2use tracing::instrument;
3
4use crate::{
5 ClaimHtlcPaymentRequest, ClaimHtlcPaymentResponse, FetchConversionLimitsRequest,
6 FetchConversionLimitsResponse, GetPaymentRequest, GetPaymentResponse,
7 RefundPendingConversionsResponse, WaitForPaymentIdentifier,
8 error::SdkError,
9 models::{
10 BuildUnsignedBatchPackageRequest, BuildUnsignedPullPackageRequest,
11 BuildUnsignedTransferPackageRequest, ListPaymentsRequest, ListPaymentsResponse, Payment,
12 PaymentRequest, PrepareSendBatchRequest, PrepareSendBatchResponse,
13 PrepareSendPaymentRequest, PrepareSendPaymentResponse, PublishSignedPullPackageRequest,
14 PublishSignedTransferPackageRequest, PublishSignedTransferPackageResponse,
15 PullPaymentResponse, ReceivePaymentRequest, ReceivePaymentResponse, SendBatchRequest,
16 SendBatchResponse, SendPaymentRequest, SendPaymentResponse, UnsignedTransferPackage,
17 },
18 utils::payments::get_payment_with_conversion_details,
19};
20
21use super::BreezSdk;
22
23pub(in crate::sdk) mod client_signing;
24pub(in crate::sdk) mod conversion;
25mod polling;
26pub(in crate::sdk) mod prepare;
27pub(in crate::sdk) mod receive;
28pub(in crate::sdk) mod send;
29pub(in crate::sdk) mod validation;
30
31#[macros::instance_span]
32#[cfg_attr(feature = "uniffi", uniffi::export(async_runtime = "tokio"))]
33#[allow(clippy::needless_pass_by_value)]
34impl BreezSdk {
35 pub async fn receive_payment(
36 &self,
37 request: ReceivePaymentRequest,
38 ) -> Result<ReceivePaymentResponse, SdkError> {
39 receive::receive_payment(self, request).await
40 }
41
42 pub async fn claim_htlc_payment(
43 &self,
44 request: ClaimHtlcPaymentRequest,
45 ) -> Result<ClaimHtlcPaymentResponse, SdkError> {
46 receive::claim_htlc_payment(self, request).await
47 }
48
49 pub async fn prepare_send_payment(
50 &self,
51 request: PrepareSendPaymentRequest,
52 ) -> Result<PrepareSendPaymentResponse, SdkError> {
53 if let PaymentRequest::CrossChain {
56 ref address,
57 ref route,
58 max_slippage_bps,
59 target_overpay_bps,
60 } = request.payment_request
61 {
62 let amount = request.amount.ok_or(SdkError::InvalidInput(
63 "Amount is required for cross-chain sends".to_string(),
64 ))?;
65 return prepare::cross_chain::prepare(
66 self,
67 address,
68 route,
69 amount,
70 request.token_identifier.clone(),
71 request.conversion_options.clone(),
72 request.fee_policy.unwrap_or_default(),
73 max_slippage_bps,
74 target_overpay_bps,
75 )
76 .await;
77 }
78 prepare::prepare(self, request).await
79 }
80
81 #[instrument(
82 level = "info",
83 target = "breez_sdk_core::send_payment",
84 skip_all,
85 fields(payment_id = tracing::field::Empty),
86 )]
87 pub async fn send_payment(
88 &self,
89 request: SendPaymentRequest,
90 ) -> Result<SendPaymentResponse, SdkError> {
91 self.maybe_ensure_spark_private_mode_initialized().await?;
92 if let Some(key) = request.idempotency_key.as_deref() {
93 tracing::Span::current().record("payment_id", key);
94 }
95 Box::pin(send::orchestrate_send(self, request, false, None)).await
96 }
97
98 pub async fn prepare_send_batch(
111 &self,
112 request: PrepareSendBatchRequest,
113 ) -> Result<PrepareSendBatchResponse, SdkError> {
114 prepare::batch::prepare(self, request).await
115 }
116
117 #[instrument(level = "info", target = "breez_sdk_core::send_batch", skip_all)]
126 pub async fn send_batch(
127 &self,
128 request: SendBatchRequest,
129 ) -> Result<SendBatchResponse, SdkError> {
130 self.maybe_ensure_spark_private_mode_initialized().await?;
131 Box::pin(send::batch::send(self, request)).await
132 }
133
134 pub async fn build_unsigned_batch_package(
140 &self,
141 request: BuildUnsignedBatchPackageRequest,
142 ) -> Result<UnsignedTransferPackage, SdkError> {
143 Box::pin(client_signing::build_unsigned_batch_package(
144 self,
145 &request.prepare_response,
146 ))
147 .await
148 }
149
150 pub async fn build_unsigned_transfer_package(
151 &self,
152 request: BuildUnsignedTransferPackageRequest,
153 ) -> Result<UnsignedTransferPackage, SdkError> {
154 Box::pin(client_signing::build_unsigned_transfer_package(
155 self,
156 &request.prepare_response,
157 request.options.as_ref(),
158 ))
159 .await
160 }
161
162 #[allow(clippy::unused_async)]
164 pub async fn build_unsigned_pull_package(
165 &self,
166 request: BuildUnsignedPullPackageRequest,
167 ) -> Result<UnsignedTransferPackage, SdkError> {
168 client_signing::build_unsigned_pull_package(&request, self.config.network)
169 }
170
171 #[instrument(
172 level = "info",
173 target = "breez_sdk_core::publish_signed_transfer_package",
174 skip_all
175 )]
176 pub async fn publish_signed_transfer_package(
177 &self,
178 request: PublishSignedTransferPackageRequest,
179 ) -> Result<PublishSignedTransferPackageResponse, SdkError> {
180 self.maybe_ensure_spark_private_mode_initialized().await?;
181 Box::pin(send::publish_signed_transfer_package(
182 self,
183 &request.signed_package,
184 ))
185 .await
186 }
187
188 #[instrument(
190 level = "info",
191 target = "breez_sdk_core::publish_signed_pull_package",
192 skip_all
193 )]
194 pub async fn publish_signed_pull_package(
195 &self,
196 request: PublishSignedPullPackageRequest,
197 ) -> Result<PullPaymentResponse, SdkError> {
198 self.maybe_ensure_spark_private_mode_initialized().await?;
199 Box::pin(client_signing::publish_signed_pull_package(
200 self,
201 &request.signed_package,
202 ))
203 .await
204 }
205
206 pub async fn fetch_conversion_limits(
207 &self,
208 request: FetchConversionLimitsRequest,
209 ) -> Result<FetchConversionLimitsResponse, SdkError> {
210 self.token_converter
211 .fetch_limits(&request)
212 .await
213 .map_err(Into::into)
214 }
215
216 pub async fn refund_pending_conversions(
227 &self,
228 ) -> Result<RefundPendingConversionsResponse, SdkError> {
229 self.token_converter
230 .refund_pending(&self.event_emitter)
231 .await
232 .map_err(Into::into)
233 }
234
235 pub async fn list_payments(
249 &self,
250 request: ListPaymentsRequest,
251 ) -> Result<ListPaymentsResponse, SdkError> {
252 use crate::utils::conversions::extract_conversion_info;
253 use crate::utils::payments::apply_conversion_details;
254
255 let mut payments = self.storage.list_payments(request.into()).await?;
256
257 let parent_ids: Vec<String> = payments
259 .iter()
260 .filter(|p| p.conversion_details.is_some())
261 .map(|p| p.id.clone())
262 .collect();
263
264 let related_payments_map = if parent_ids.is_empty() {
265 std::collections::HashMap::default()
266 } else {
267 self.storage.get_payments_by_parent_ids(parent_ids).await?
268 };
269
270 for payment in &mut payments {
271 let has_conversion_details = payment.conversion_details.is_some();
272 let has_crosschain_info = extract_conversion_info(payment.details.clone())
273 .is_some_and(|info| !matches!(info, crate::ConversionInfo::Amm { .. }));
274
275 if !has_conversion_details && !has_crosschain_info {
276 continue;
277 }
278
279 let child_payments = if has_conversion_details {
280 related_payments_map.get(&payment.id).map(Vec::as_slice)
281 } else {
282 None
283 };
284
285 apply_conversion_details(payment, child_payments);
286 }
287
288 Ok(ListPaymentsResponse { payments })
289 }
290
291 pub async fn get_payment(
292 &self,
293 request: GetPaymentRequest,
294 ) -> Result<GetPaymentResponse, SdkError> {
295 let payment =
296 get_payment_with_conversion_details(request.payment_id, self.storage.clone()).await?;
297
298 Ok(GetPaymentResponse { payment })
299 }
300}
301
302impl BreezSdk {
304 pub(crate) async fn receive_bolt11_invoice_inner(
305 &self,
306 description: String,
307 amount_sats: Option<u64>,
308 expiry_secs: Option<u32>,
309 payment_hash: Option<String>,
310 receiver_identity_public_key: Option<String>,
311 preimage: Option<String>,
312 ) -> Result<LightningReceivePayment, SdkError> {
313 receive::receive_bolt11_invoice_inner(
314 self,
315 description,
316 amount_sats,
317 expiry_secs,
318 payment_hash,
319 receiver_identity_public_key,
320 preimage,
321 )
322 .await
323 }
324
325 pub(crate) async fn wait_for_incoming_payment(
326 &self,
327 identifier: WaitForPaymentIdentifier,
328 completion_timeout_secs: u32,
329 ) -> Result<Payment, SdkError> {
330 polling::wait_for_incoming_payment(self, identifier, completion_timeout_secs).await
331 }
332
333 pub(crate) async fn finalize_payment(&self, payment: Payment) -> bool {
334 polling::finalize_payment(self, payment).await
335 }
336}