1use breez_sdk_common::{
2 input::{self, BitcoinAddressDetails, Bolt11InvoiceDetails},
3 lnurl::pay::{LnurlPayRequestDetails, SuccessAction, SuccessActionProcessed},
4 network::BitcoinNetwork,
5};
6use core::fmt;
7use serde::{Deserialize, Serialize};
8use spark_wallet::{
9 CoopExitFeeQuote, CoopExitSpeedFeeQuote, ExitSpeed, LightningSendPayment, LightningSendStatus,
10 Network as SparkNetwork, SspUserRequest, TransferDirection, TransferStatus, TransferType,
11 WalletTransfer,
12};
13use std::time::UNIX_EPOCH;
14
15use crate::{SdkError, error::DepositClaimError};
16
17pub struct ConnectRequest {
18 pub config: Config,
19 pub mnemonic: String,
20 pub storage_dir: String,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
25#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
26pub enum PaymentType {
27 Send,
29 Receive,
31}
32
33impl fmt::Display for PaymentType {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 PaymentType::Send => write!(f, "send"),
37 PaymentType::Receive => write!(f, "receive"),
38 }
39 }
40}
41
42impl From<&str> for PaymentType {
43 fn from(s: &str) -> Self {
44 match s {
45 "receive" => PaymentType::Receive,
46 _ => PaymentType::Send, }
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
53#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
54pub enum PaymentStatus {
55 Completed,
57 Pending,
59 Failed,
61}
62
63impl fmt::Display for PaymentStatus {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 match self {
66 PaymentStatus::Completed => write!(f, "completed"),
67 PaymentStatus::Pending => write!(f, "pending"),
68 PaymentStatus::Failed => write!(f, "failed"),
69 }
70 }
71}
72
73impl From<&str> for PaymentStatus {
74 fn from(s: &str) -> Self {
75 match s {
76 "completed" => PaymentStatus::Completed,
77 "failed" => PaymentStatus::Failed,
78 _ => PaymentStatus::Pending, }
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
84#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
85pub enum PaymentMethod {
86 Lightning,
87 Spark,
88 Deposit,
89 Withdraw,
90 Unknown,
91}
92
93impl From<TransferType> for PaymentMethod {
94 fn from(value: TransferType) -> Self {
95 match value {
96 TransferType::PreimageSwap => PaymentMethod::Lightning,
97 TransferType::CooperativeExit => PaymentMethod::Withdraw,
98 TransferType::Transfer => PaymentMethod::Spark,
99 TransferType::UtxoSwap => PaymentMethod::Deposit,
100 _ => PaymentMethod::Unknown,
101 }
102 }
103}
104#[derive(Debug, Clone, Serialize, Deserialize)]
106#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
107pub struct Payment {
108 pub id: String,
110 pub payment_type: PaymentType,
112 pub status: PaymentStatus,
114 pub amount: u64,
116 pub fees: u64,
118 pub timestamp: u64,
120 pub method: PaymentMethod,
123 pub details: Option<PaymentDetails>,
125}
126
127#[allow(clippy::large_enum_variant)]
130#[derive(Debug, Clone, Serialize, Deserialize)]
131#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
132pub enum PaymentDetails {
133 Spark,
134 Lightning {
135 description: Option<String>,
137 preimage: Option<String>,
139 invoice: String,
143
144 payment_hash: String,
146
147 destination_pubkey: String,
149
150 lnurl_pay_info: Option<LnurlPayInfo>,
152 },
153 Withdraw {
154 tx_id: String,
155 },
156 Deposit {
157 tx_id: String,
158 },
159}
160
161impl TryFrom<SspUserRequest> for PaymentDetails {
162 type Error = SdkError;
163 fn try_from(user_request: SspUserRequest) -> Result<Self, Self::Error> {
164 let details = match user_request {
165 SspUserRequest::CoopExitRequest(request) => PaymentDetails::Withdraw {
166 tx_id: request.coop_exit_txid,
167 },
168 SspUserRequest::LeavesSwapRequest(_) => PaymentDetails::Spark,
169 SspUserRequest::LightningReceiveRequest(request) => {
170 let invoice_details = input::parse_invoice(&request.invoice.encoded_invoice)
171 .ok_or(SdkError::Generic(
172 "Invalid invoice in SspUserRequest::LightningReceiveRequest".to_string(),
173 ))?;
174 PaymentDetails::Lightning {
175 description: invoice_details.description,
176 preimage: request.lightning_receive_payment_preimage,
177 invoice: request.invoice.encoded_invoice,
178 payment_hash: request.invoice.payment_hash,
179 destination_pubkey: invoice_details.payee_pubkey,
180 lnurl_pay_info: None,
181 }
182 }
183 SspUserRequest::LightningSendRequest(request) => {
184 let invoice_details =
185 input::parse_invoice(&request.encoded_invoice).ok_or(SdkError::Generic(
186 "Invalid invoice in SspUserRequest::LightningSendRequest".to_string(),
187 ))?;
188 PaymentDetails::Lightning {
189 description: invoice_details.description,
190 preimage: request.lightning_send_payment_preimage,
191 invoice: request.encoded_invoice,
192 payment_hash: invoice_details.payment_hash,
193 destination_pubkey: invoice_details.payee_pubkey,
194 lnurl_pay_info: None,
195 }
196 }
197 SspUserRequest::ClaimStaticDeposit(request) => PaymentDetails::Deposit {
198 tx_id: request.transaction_id,
199 },
200 };
201 Ok(details)
202 }
203}
204
205impl TryFrom<WalletTransfer> for Payment {
206 type Error = SdkError;
207 fn try_from(transfer: WalletTransfer) -> Result<Self, Self::Error> {
208 let payment_type = match transfer.direction {
209 TransferDirection::Incoming => PaymentType::Receive,
210 TransferDirection::Outgoing => PaymentType::Send,
211 };
212 let mut status = match transfer.status {
213 TransferStatus::Completed => PaymentStatus::Completed,
214 TransferStatus::SenderKeyTweaked
215 if transfer.direction == TransferDirection::Outgoing =>
216 {
217 PaymentStatus::Completed
218 }
219 TransferStatus::Expired | TransferStatus::Returned => PaymentStatus::Failed,
220 _ => PaymentStatus::Pending,
221 };
222 let (fees_sat, mut amount_sat): (u64, u64) = match transfer.clone().user_request {
223 Some(user_request) => match user_request {
224 SspUserRequest::LightningSendRequest(r) => {
225 if r.lightning_send_payment_preimage.is_some() {
228 status = PaymentStatus::Completed;
229 }
230 let fee_sat = r.fee.as_sats().unwrap_or(0);
231 (fee_sat, transfer.total_value_sat.saturating_sub(fee_sat))
232 }
233 SspUserRequest::CoopExitRequest(r) => {
234 let fee_sat = r
235 .fee
236 .as_sats()
237 .unwrap_or(0)
238 .saturating_add(r.l1_broadcast_fee.as_sats().unwrap_or(0));
239 (fee_sat, transfer.total_value_sat.saturating_sub(fee_sat))
240 }
241 SspUserRequest::ClaimStaticDeposit(r) => {
242 let fee_sat = r.max_fee.as_sats().unwrap_or(0);
243 (fee_sat, transfer.total_value_sat)
244 }
245 _ => (0, transfer.total_value_sat),
246 },
247 None => (0, transfer.total_value_sat),
248 };
249
250 let details: Option<PaymentDetails> = if let Some(user_request) = transfer.user_request {
251 Some(user_request.try_into()?)
252 } else {
253 if status == PaymentStatus::Completed
256 && [
257 TransferType::CooperativeExit,
258 TransferType::PreimageSwap,
259 TransferType::UtxoSwap,
260 ]
261 .contains(&transfer.transfer_type)
262 {
263 status = PaymentStatus::Pending;
264 }
265 amount_sat = transfer.total_value_sat;
266 None
267 };
268
269 Ok(Payment {
270 id: transfer.id.to_string(),
271 payment_type,
272 status,
273 amount: amount_sat,
274 fees: fees_sat,
275 timestamp: match transfer.created_at.map(|t| t.duration_since(UNIX_EPOCH)) {
276 Some(Ok(duration)) => duration.as_secs(),
277 _ => 0,
278 },
279 method: transfer.transfer_type.into(),
280 details,
281 })
282 }
283}
284
285impl Payment {
286 pub fn from_lightning(
287 payment: LightningSendPayment,
288 amount_sat: u64,
289 ) -> Result<Self, SdkError> {
290 let status = match payment.status {
291 LightningSendStatus::LightningPaymentSucceeded => PaymentStatus::Completed,
292 LightningSendStatus::LightningPaymentFailed => PaymentStatus::Failed,
293 _ => PaymentStatus::Pending,
294 };
295
296 let invoice_details = input::parse_invoice(&payment.encoded_invoice).ok_or(
297 SdkError::Generic("Invalid invoice in LightnintSendPayment".to_string()),
298 )?;
299 let details = PaymentDetails::Lightning {
300 description: invoice_details.description,
301 preimage: payment.payment_preimage,
302 invoice: payment.encoded_invoice,
303 payment_hash: invoice_details.payment_hash,
304 destination_pubkey: invoice_details.payee_pubkey,
305 lnurl_pay_info: None,
306 };
307
308 Ok(Payment {
309 id: payment.id,
310 payment_type: PaymentType::Send,
311 status,
312 amount: amount_sat,
313 fees: payment.fee_sat,
314 timestamp: payment.created_at.cast_unsigned(),
315 method: PaymentMethod::Lightning,
316 details: Some(details),
317 })
318 }
319}
320
321#[derive(Debug, Clone, Copy)]
322#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
323pub enum Network {
324 Mainnet,
325 Regtest,
326}
327
328impl std::fmt::Display for Network {
329 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
330 match self {
331 Network::Mainnet => write!(f, "Mainnet"),
332 Network::Regtest => write!(f, "Regtest"),
333 }
334 }
335}
336
337impl From<Network> for SparkNetwork {
338 fn from(network: Network) -> Self {
339 match network {
340 Network::Mainnet => SparkNetwork::Mainnet,
341 Network::Regtest => SparkNetwork::Regtest,
342 }
343 }
344}
345
346impl From<Network> for BitcoinNetwork {
347 fn from(network: Network) -> Self {
348 match network {
349 Network::Mainnet => BitcoinNetwork::Bitcoin,
350 Network::Regtest => BitcoinNetwork::Regtest,
351 }
352 }
353}
354
355#[derive(Debug, Clone)]
356#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
357pub struct Config {
358 pub api_key: Option<String>,
359 pub network: Network,
360 pub sync_interval_secs: u32,
361
362 pub max_deposit_claim_fee: Option<Fee>,
365}
366
367#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
368#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
369pub enum Fee {
370 Fixed { amount: u64 },
372 Rate { sat_per_vbyte: u64 },
374}
375
376impl Fee {
377 pub fn to_sats(&self, vbytes: u64) -> u64 {
378 match self {
379 Fee::Fixed { amount } => *amount,
380 Fee::Rate { sat_per_vbyte } => sat_per_vbyte.saturating_mul(vbytes),
381 }
382 }
383}
384
385impl From<Fee> for spark_wallet::Fee {
386 fn from(fee: Fee) -> Self {
387 match fee {
388 Fee::Fixed { amount } => spark_wallet::Fee::Fixed { amount },
389 Fee::Rate { sat_per_vbyte } => spark_wallet::Fee::Rate { sat_per_vbyte },
390 }
391 }
392}
393
394#[derive(Debug, Clone, Serialize, Deserialize)]
395#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
396pub struct DepositInfo {
397 pub txid: String,
398 pub vout: u32,
399 pub amount_sats: u64,
400 pub refund_tx: Option<String>,
401 pub refund_tx_id: Option<String>,
402 pub claim_error: Option<DepositClaimError>,
403}
404
405#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
406pub struct ClaimDepositRequest {
407 pub txid: String,
408 pub vout: u32,
409 pub max_fee: Option<Fee>,
410}
411
412#[derive(Debug, Clone, Serialize)]
413#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
414pub struct ClaimDepositResponse {
415 pub payment: Payment,
416}
417
418#[derive(Debug, Clone, Serialize)]
419#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
420pub struct RefundDepositRequest {
421 pub txid: String,
422 pub vout: u32,
423 pub destination_address: String,
424 pub fee: Fee,
425}
426
427#[derive(Debug, Clone, Serialize)]
428#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
429pub struct RefundDepositResponse {
430 pub tx_id: String,
431 pub tx_hex: String,
432}
433
434#[derive(Debug, Clone, Serialize)]
435#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
436pub struct ListUnclaimedDepositsRequest {}
437
438#[derive(Debug, Clone, Serialize)]
439#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
440pub struct ListUnclaimedDepositsResponse {
441 pub deposits: Vec<DepositInfo>,
442}
443
444impl std::fmt::Display for Fee {
445 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
446 match self {
447 Fee::Fixed { amount } => write!(f, "Fixed: {amount}"),
448 Fee::Rate { sat_per_vbyte } => write!(f, "Rate: {sat_per_vbyte}"),
449 }
450 }
451}
452
453#[derive(Debug, Clone)]
454#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
455pub struct Credentials {
456 pub username: String,
457 pub password: String,
458}
459
460#[derive(Debug, Clone)]
462#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
463pub struct GetInfoRequest {}
464
465#[derive(Debug, Clone, Serialize)]
467#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
468pub struct GetInfoResponse {
469 pub balance_sats: u64,
471}
472
473#[derive(Debug, Clone)]
475#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
476pub struct SyncWalletRequest {}
477
478#[derive(Debug, Clone, Serialize)]
480#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
481pub struct SyncWalletResponse {}
482
483#[derive(Debug, Clone, Serialize)]
484#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
485pub enum ReceivePaymentMethod {
486 SparkAddress,
487 BitcoinAddress,
488 Bolt11Invoice {
489 description: String,
490 amount_sats: Option<u64>,
491 },
492}
493
494#[derive(Debug, Clone, Serialize)]
495#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
496pub enum SendPaymentMethod {
497 BitcoinAddress {
498 address: BitcoinAddressDetails,
499 fee_quote: SendOnchainFeeQuote,
500 },
501 Bolt11Invoice {
502 invoice_details: Bolt11InvoiceDetails,
503 spark_transfer_fee_sats: Option<u64>,
504 lightning_fee_sats: u64,
505 }, SparkAddress {
507 address: String,
508 fee_sats: u64,
509 },
510}
511
512#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
513#[derive(Debug, Clone, Serialize)]
514pub struct SendOnchainFeeQuote {
515 pub id: String,
516 pub expires_at: u64,
517 pub speed_fast: SendOnchainSpeedFeeQuote,
518 pub speed_medium: SendOnchainSpeedFeeQuote,
519 pub speed_slow: SendOnchainSpeedFeeQuote,
520}
521
522impl From<CoopExitFeeQuote> for SendOnchainFeeQuote {
523 fn from(value: CoopExitFeeQuote) -> Self {
524 Self {
525 id: value.id,
526 expires_at: value.expires_at,
527 speed_fast: value.speed_fast.into(),
528 speed_medium: value.speed_medium.into(),
529 speed_slow: value.speed_slow.into(),
530 }
531 }
532}
533
534impl From<SendOnchainFeeQuote> for CoopExitFeeQuote {
535 fn from(value: SendOnchainFeeQuote) -> Self {
536 Self {
537 id: value.id,
538 expires_at: value.expires_at,
539 speed_fast: value.speed_fast.into(),
540 speed_medium: value.speed_medium.into(),
541 speed_slow: value.speed_slow.into(),
542 }
543 }
544}
545#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
546#[derive(Debug, Clone, Serialize)]
547pub struct SendOnchainSpeedFeeQuote {
548 pub user_fee_sat: u64,
549 pub l1_broadcast_fee_sat: u64,
550}
551
552impl SendOnchainSpeedFeeQuote {
553 pub fn total_fee_sat(&self) -> u64 {
554 self.user_fee_sat.saturating_add(self.l1_broadcast_fee_sat)
555 }
556}
557
558impl From<CoopExitSpeedFeeQuote> for SendOnchainSpeedFeeQuote {
559 fn from(value: CoopExitSpeedFeeQuote) -> Self {
560 Self {
561 user_fee_sat: value.user_fee_sat,
562 l1_broadcast_fee_sat: value.l1_broadcast_fee_sat,
563 }
564 }
565}
566
567impl From<SendOnchainSpeedFeeQuote> for CoopExitSpeedFeeQuote {
568 fn from(value: SendOnchainSpeedFeeQuote) -> Self {
569 Self {
570 user_fee_sat: value.user_fee_sat,
571 l1_broadcast_fee_sat: value.l1_broadcast_fee_sat,
572 }
573 }
574}
575
576#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
577pub struct ReceivePaymentRequest {
578 pub payment_method: ReceivePaymentMethod,
579}
580
581#[derive(Debug, Clone, Serialize)]
582#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
583pub struct ReceivePaymentResponse {
584 pub payment_request: String,
585 pub fee_sats: u64,
586}
587
588#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
589pub struct PrepareLnurlPayRequest {
590 pub amount_sats: u64,
591 pub comment: Option<String>,
592 pub pay_request: LnurlPayRequestDetails,
593 pub validate_success_action_url: Option<bool>,
594}
595
596#[derive(Debug)]
597#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
598pub struct PrepareLnurlPayResponse {
599 pub amount_sats: u64,
600 pub comment: Option<String>,
601 pub pay_request: LnurlPayRequestDetails,
602 pub fee_sats: u64,
603 pub invoice_details: Bolt11InvoiceDetails,
604 pub success_action: Option<SuccessAction>,
605}
606
607#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
608pub struct LnurlPayRequest {
609 pub prepare_response: PrepareLnurlPayResponse,
610}
611
612#[derive(Debug, Serialize)]
613#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
614pub struct LnurlPayResponse {
615 pub payment: Payment,
616 pub success_action: Option<SuccessActionProcessed>,
617}
618
619#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
621#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
622pub struct LnurlPayInfo {
623 pub ln_address: Option<String>,
624 pub comment: Option<String>,
625 pub domain: Option<String>,
626 pub metadata: Option<String>,
627 pub processed_success_action: Option<SuccessActionProcessed>,
628 pub raw_success_action: Option<SuccessAction>,
629}
630
631#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
632#[derive(Debug, Clone, Serialize)]
633pub enum OnchainConfirmationSpeed {
634 Fast,
635 Medium,
636 Slow,
637}
638
639impl From<OnchainConfirmationSpeed> for ExitSpeed {
640 fn from(speed: OnchainConfirmationSpeed) -> Self {
641 match speed {
642 OnchainConfirmationSpeed::Fast => ExitSpeed::Fast,
643 OnchainConfirmationSpeed::Medium => ExitSpeed::Medium,
644 OnchainConfirmationSpeed::Slow => ExitSpeed::Slow,
645 }
646 }
647}
648
649impl From<ExitSpeed> for OnchainConfirmationSpeed {
650 fn from(speed: ExitSpeed) -> Self {
651 match speed {
652 ExitSpeed::Fast => OnchainConfirmationSpeed::Fast,
653 ExitSpeed::Medium => OnchainConfirmationSpeed::Medium,
654 ExitSpeed::Slow => OnchainConfirmationSpeed::Slow,
655 }
656 }
657}
658
659#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
660pub struct PrepareSendPaymentRequest {
661 pub payment_request: String,
662 pub amount_sats: Option<u64>,
663}
664
665#[derive(Debug, Clone, Serialize)]
666#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
667pub struct PrepareSendPaymentResponse {
668 pub payment_method: SendPaymentMethod,
669 pub amount_sats: u64,
670}
671
672#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
673pub enum SendPaymentOptions {
674 BitcoinAddress {
675 confirmation_speed: OnchainConfirmationSpeed,
676 },
677 Bolt11Invoice {
678 use_spark: bool,
679 },
680}
681
682#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
683pub struct SendPaymentRequest {
684 pub prepare_response: PrepareSendPaymentResponse,
685 pub options: Option<SendPaymentOptions>,
686}
687
688#[derive(Debug, Clone, Serialize)]
689#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
690pub struct SendPaymentResponse {
691 pub payment: Payment,
692}
693
694#[derive(Debug, Clone)]
696#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
697pub struct ListPaymentsRequest {
698 pub offset: Option<u32>,
700 pub limit: Option<u32>,
702}
703
704#[derive(Debug, Clone, Serialize)]
706#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
707pub struct ListPaymentsResponse {
708 pub payments: Vec<Payment>,
710}
711
712#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
713pub struct GetPaymentRequest {
714 pub payment_id: String,
715}
716
717#[derive(Debug, Clone, Serialize)]
718#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
719pub struct GetPaymentResponse {
720 pub payment: Payment,
721}
722
723#[cfg_attr(feature = "uniffi", uniffi::export(callback_interface))]
724pub trait Logger: Send + Sync {
725 fn log(&self, l: LogEntry);
726}
727
728#[derive(Debug, Clone, Serialize)]
729#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
730pub struct LogEntry {
731 pub line: String,
732 pub level: String,
733}