breez_sdk_spark/models/mod.rs
1pub(crate) mod adaptors;
2pub mod payment_observer;
3pub use payment_observer::*;
4
5// Re-export public conversion types from the conversion module
6pub use crate::token_conversion::{
7 AmountAdjustmentReason, ConversionEstimate, ConversionInfo, ConversionOptions,
8 ConversionPurpose, ConversionStatus, ConversionType, FetchConversionLimitsRequest,
9 FetchConversionLimitsResponse, SwapDegradation,
10};
11
12use core::fmt;
13use lnurl_models::RecoverLnurlPayResponse;
14use serde::{Deserialize, Serialize};
15use serde_json::Value;
16use std::{
17 collections::{HashMap, HashSet},
18 fmt::Display,
19 str::FromStr,
20};
21
22use crate::{
23 BitcoinAddressDetails, BitcoinChainService, BitcoinNetwork, Bolt11InvoiceDetails,
24 ExternalInputParser, FiatCurrency, LnurlPayRequestDetails, LnurlWithdrawRequestDetails, Rate,
25 SdkError, SparkInvoiceDetails, SuccessAction, SuccessActionProcessed,
26 cross_chain::{CrossChainFeeMode, CrossChainProviderContext, CrossChainRoutePair},
27 error::DepositClaimError,
28};
29
30/// A list of external input parsers that are used by default.
31/// To opt-out, set `use_default_external_input_parsers` in [Config] to false.
32pub const DEFAULT_EXTERNAL_INPUT_PARSERS: &[(&str, &str, &str)] = &[
33 (
34 "picknpay",
35 "(.*)(za.co.electrum.picknpay)(.*)",
36 "https://cryptoqr.net/.well-known/lnurlp/<input>",
37 ),
38 (
39 "bootleggers",
40 r"(.*)(wigroup\.co|yoyogroup\.co)(.*)",
41 "https://cryptoqr.net/.well-known/lnurlw/<input>",
42 ),
43];
44
45/// Represents the seed for wallet generation, either as a mnemonic phrase with an optional
46/// passphrase or as raw entropy bytes.
47#[derive(Debug, Clone)]
48#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
49pub enum Seed {
50 /// A BIP-39 mnemonic phrase with an optional passphrase.
51 Mnemonic {
52 /// The mnemonic phrase. 12 or 24 words.
53 mnemonic: String,
54 /// An optional passphrase for the mnemonic.
55 passphrase: Option<String>,
56 },
57 /// Raw entropy bytes.
58 Entropy(Vec<u8>),
59}
60
61impl Seed {
62 pub fn to_bytes(&self) -> Result<Vec<u8>, SdkError> {
63 match self {
64 Seed::Mnemonic {
65 mnemonic,
66 passphrase,
67 } => {
68 let mnemonic = bip39::Mnemonic::parse(mnemonic)
69 .map_err(|e| SdkError::Generic(e.to_string()))?;
70
71 Ok(mnemonic
72 .to_seed(passphrase.as_deref().unwrap_or(""))
73 .to_vec())
74 }
75 Seed::Entropy(entropy) => Ok(entropy.clone()),
76 }
77 }
78}
79
80#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
81pub struct ConnectRequest {
82 pub config: Config,
83 pub seed: Seed,
84 pub storage_dir: String,
85}
86
87/// Request object for connecting to the Spark network using an external signer.
88///
89/// This allows using a custom signer implementation instead of providing a seed directly.
90#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
91#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
92pub struct ConnectWithSignerRequest {
93 pub config: Config,
94 /// External signer for non-Spark SDK signing (LNURL-auth, sync, message
95 /// signing, ECIES).
96 pub breez_signer: std::sync::Arc<dyn crate::signer::ExternalBreezSigner>,
97 /// External high-level Spark signer for the Spark wallet flows.
98 pub spark_signer: std::sync::Arc<dyn crate::signer::ExternalSparkSigner>,
99 pub storage_dir: String,
100}
101
102/// Request object for connecting to the Spark network using a signing-only
103/// external signer.
104///
105/// Use this instead of [`ConnectWithSignerRequest`] for a signer that can't
106/// perform the SDK's local ECIES/HMAC operations (for example a
107/// policy-restricted enclave). The SDK keeps session tokens in plaintext and
108/// disables the features that rely on ECIES/HMAC.
109#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
110#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
111pub struct ConnectWithSigningOnlySignerRequest {
112 pub config: Config,
113 /// Signing-only external signer for non-Spark SDK signing.
114 pub breez_signer: std::sync::Arc<dyn crate::signer::ExternalSigningSigner>,
115 /// External high-level Spark signer for the Spark wallet flows.
116 pub spark_signer: std::sync::Arc<dyn crate::signer::ExternalSparkSigner>,
117 pub storage_dir: String,
118}
119
120/// The type of payment
121#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
122#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
123pub enum PaymentType {
124 /// Payment sent from this wallet
125 Send,
126 /// Payment received to this wallet
127 Receive,
128}
129
130impl fmt::Display for PaymentType {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 match self {
133 PaymentType::Send => write!(f, "send"),
134 PaymentType::Receive => write!(f, "receive"),
135 }
136 }
137}
138
139impl FromStr for PaymentType {
140 type Err = String;
141
142 fn from_str(s: &str) -> Result<Self, Self::Err> {
143 Ok(match s.to_lowercase().as_str() {
144 "receive" => PaymentType::Receive,
145 "send" => PaymentType::Send,
146 _ => return Err(format!("invalid payment type '{s}'")),
147 })
148 }
149}
150
151/// The status of a payment
152#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
153#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
154pub enum PaymentStatus {
155 /// Payment is completed successfully
156 Completed,
157 /// Payment is in progress
158 Pending,
159 /// Payment has failed
160 Failed,
161}
162
163impl PaymentStatus {
164 /// Returns true if the payment status is final (either Completed or Failed)
165 pub fn is_final(&self) -> bool {
166 matches!(self, PaymentStatus::Completed | PaymentStatus::Failed)
167 }
168}
169
170impl fmt::Display for PaymentStatus {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 match self {
173 PaymentStatus::Completed => write!(f, "completed"),
174 PaymentStatus::Pending => write!(f, "pending"),
175 PaymentStatus::Failed => write!(f, "failed"),
176 }
177 }
178}
179
180impl FromStr for PaymentStatus {
181 type Err = String;
182
183 fn from_str(s: &str) -> Result<Self, Self::Err> {
184 Ok(match s.to_lowercase().as_str() {
185 "completed" => PaymentStatus::Completed,
186 "pending" => PaymentStatus::Pending,
187 "failed" => PaymentStatus::Failed,
188 _ => return Err(format!("Invalid payment status '{s}'")),
189 })
190 }
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
194#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
195pub enum PaymentMethod {
196 Lightning,
197 Spark,
198 Token,
199 Deposit,
200 Withdraw,
201 Unknown,
202}
203
204impl Display for PaymentMethod {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 match self {
207 PaymentMethod::Lightning => write!(f, "lightning"),
208 PaymentMethod::Spark => write!(f, "spark"),
209 PaymentMethod::Token => write!(f, "token"),
210 PaymentMethod::Deposit => write!(f, "deposit"),
211 PaymentMethod::Withdraw => write!(f, "withdraw"),
212 PaymentMethod::Unknown => write!(f, "unknown"),
213 }
214 }
215}
216
217impl FromStr for PaymentMethod {
218 type Err = ();
219
220 fn from_str(s: &str) -> Result<Self, Self::Err> {
221 match s {
222 "lightning" => Ok(PaymentMethod::Lightning),
223 "spark" => Ok(PaymentMethod::Spark),
224 "token" => Ok(PaymentMethod::Token),
225 "deposit" => Ok(PaymentMethod::Deposit),
226 "withdraw" => Ok(PaymentMethod::Withdraw),
227 "unknown" => Ok(PaymentMethod::Unknown),
228 _ => Err(()),
229 }
230 }
231}
232
233/// Represents a payment (sent or received)
234#[derive(Debug, Clone, Serialize, Deserialize)]
235#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
236pub struct Payment {
237 /// Unique identifier for the payment
238 pub id: String,
239 /// Type of payment (send or receive)
240 pub payment_type: PaymentType,
241 /// Status of the payment
242 pub status: PaymentStatus,
243 /// Amount in satoshis or token base units
244 pub amount: u128,
245 /// Fee paid in satoshis or token base units
246 pub fees: u128,
247 /// Timestamp of when the payment was created
248 pub timestamp: u64,
249 /// Method of payment. Sometimes the payment details is empty so this field
250 /// is used to determine the payment method.
251 pub method: PaymentMethod,
252 /// Details of the payment
253 pub details: Option<PaymentDetails>,
254 /// If set, this payment involved a conversion before the payment
255 pub conversion_details: Option<ConversionDetails>,
256}
257
258impl Payment {
259 /// Returns `true` if this payment is a child of a conversion operation.
260 ///
261 /// Conversion operations (stable balance, ongoing sends) create internal child
262 /// payments (send sats→Flashnet, receive tokens). These are identified by having
263 /// `conversion_info` set in their payment details.
264 pub fn is_conversion_child(&self) -> bool {
265 matches!(
266 &self.details,
267 Some(
268 PaymentDetails::Spark {
269 conversion_info: Some(_),
270 ..
271 } | PaymentDetails::Token {
272 conversion_info: Some(_),
273 ..
274 }
275 )
276 )
277 }
278}
279
280/// Outlines the steps involved in one or more conversions on a payment.
281///
282/// Built progressively: `status` is available immediately from payment metadata,
283/// while `conversions` are enriched later from child payments and conversion info.
284#[derive(Debug, Clone, Serialize, Deserialize)]
285#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
286pub struct ConversionDetails {
287 /// Overall status of the conversion (persisted in storage)
288 pub status: ConversionStatus,
289 /// Ordered list of conversion steps. For sends: [AMM, cross-chain].
290 /// For receives: [cross-chain, AMM]. Rebuilt on retrieval, not persisted.
291 #[serde(default)]
292 pub conversions: Vec<Conversion>,
293}
294
295/// The provider that performed a conversion.
296#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
297#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
298pub enum ConversionProvider {
299 /// AMM (Flashnet pool) conversion between token and BTC on Spark
300 Amm,
301 /// Orchestra cross-chain conversion
302 Orchestra,
303 /// Boltz reverse-swap cross-chain conversion
304 Boltz,
305}
306
307/// The chain or network that a [`ConversionSide`] lives on.
308#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
309#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
310pub enum ConversionChain {
311 /// Spark layer-2 network.
312 Spark,
313 /// Bitcoin Lightning Network.
314 Lightning,
315 /// An external chain reached via a cross-chain provider.
316 External {
317 /// Human-readable chain name (e.g. `"base"`, `"solana"`, `"arbitrum"`).
318 name: String,
319 /// Stable chain identifier (e.g. EVM `chainId` as a decimal string,
320 /// or a chain-native identifier). `None` when the provider does not
321 /// expose one for this route.
322 chain_id: Option<String>,
323 },
324}
325
326/// The asset on a [`ConversionSide`] — groups the ticker, stable identifier,
327/// and decimals that always travel together.
328#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
329#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
330pub struct ConversionAsset {
331 /// Ticker (e.g. `"BTC"`, `"USDB"`, `"USDC"`, `"USDT"`). Tickers alone
332 /// are ambiguous across chains — pair with [`Self::identifier`] for a
333 /// hard match.
334 pub ticker: String,
335 /// Stable identifier: a Spark token identifier for Spark tokens, or a
336 /// contract/mint address for cross-chain assets. `None` for BTC/sats.
337 pub identifier: Option<String>,
338 /// Number of decimals for the asset.
339 /// `0` for BTC/sats sides (amount is already in the smallest unit,
340 /// so no scaling is needed); non-zero for token assets (e.g. `6` for
341 /// USDC/USDT/USDB).
342 pub decimals: u32,
343}
344
345/// One side (source or destination) of a conversion.
346#[derive(Debug, Clone, Serialize, Deserialize)]
347#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
348pub struct ConversionSide {
349 /// The chain or network for this side.
350 pub chain: ConversionChain,
351 /// The asset being converted on this side.
352 pub asset: ConversionAsset,
353 /// Amount in base units (satoshis or token base units)
354 pub amount: u128,
355 /// Fee in the same base units
356 pub fee: u128,
357}
358
359/// A single conversion in a payment's conversion chain.
360#[derive(Debug, Clone, Serialize, Deserialize)]
361#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
362pub struct Conversion {
363 /// The provider that performed this conversion
364 pub provider: ConversionProvider,
365 /// Status of this specific conversion step
366 pub status: ConversionStatus,
367 /// Source side of the conversion
368 pub from: ConversionSide,
369 /// Destination side of the conversion
370 pub to: ConversionSide,
371 /// Reason the conversion amount was adjusted, if applicable (AMM only)
372 #[serde(default)]
373 pub amount_adjustment: Option<AmountAdjustmentReason>,
374}
375
376#[cfg(feature = "uniffi")]
377uniffi::custom_type!(u128, String, {
378 remote,
379 try_lift: |val| val.parse::<u128>().map_err(uniffi::deps::anyhow::Error::msg),
380 lower: |obj| obj.to_string(),
381});
382
383// TODO: fix large enum variant lint - may be done by boxing lnurl_pay_info but that requires
384// some changes to the wasm bindgen macro
385#[allow(clippy::large_enum_variant)]
386#[derive(Debug, Clone, Serialize, Deserialize)]
387#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
388pub enum PaymentDetails {
389 Spark {
390 /// The invoice details if the payment fulfilled a spark invoice
391 invoice_details: Option<SparkInvoicePaymentDetails>,
392 /// The HTLC transfer details if the payment fulfilled an HTLC transfer
393 htlc_details: Option<SparkHtlcDetails>,
394 /// The information for a conversion
395 conversion_info: Option<ConversionInfo>,
396 },
397 Token {
398 metadata: TokenMetadata,
399 tx_hash: String,
400 tx_type: TokenTransactionType,
401 /// The invoice details if the payment fulfilled a spark invoice
402 invoice_details: Option<SparkInvoicePaymentDetails>,
403 /// The information for a conversion
404 conversion_info: Option<ConversionInfo>,
405 },
406 Lightning {
407 /// Represents the invoice description
408 description: Option<String>,
409 /// Represents the Bolt11/Bolt12 invoice associated with a payment
410 /// In the case of a Send payment, this is the invoice paid by the user
411 /// In the case of a Receive payment, this is the invoice paid to the user
412 invoice: String,
413
414 /// The invoice destination/payee pubkey
415 destination_pubkey: String,
416
417 /// The HTLC transfer details
418 htlc_details: SparkHtlcDetails,
419
420 /// Lnurl payment information if this was an lnurl payment.
421 lnurl_pay_info: Option<LnurlPayInfo>,
422
423 /// Lnurl withdrawal information if this was an lnurl payment.
424 lnurl_withdraw_info: Option<LnurlWithdrawInfo>,
425
426 /// Lnurl receive information if this was a received lnurl payment.
427 lnurl_receive_metadata: Option<LnurlReceiveMetadata>,
428
429 /// The information for a conversion — populated when this Lightning
430 /// payment is the source leg of a cross-chain conversion (e.g. a
431 /// Boltz reverse swap paying a hold invoice).
432 conversion_info: Option<ConversionInfo>,
433 },
434 Withdraw {
435 tx_id: String,
436 },
437 Deposit {
438 tx_id: String,
439 vout: u32,
440 },
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
444#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
445pub enum TokenTransactionType {
446 Transfer,
447 Mint,
448 Burn,
449}
450
451impl fmt::Display for TokenTransactionType {
452 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
453 match self {
454 TokenTransactionType::Transfer => write!(f, "transfer"),
455 TokenTransactionType::Mint => write!(f, "mint"),
456 TokenTransactionType::Burn => write!(f, "burn"),
457 }
458 }
459}
460
461impl FromStr for TokenTransactionType {
462 type Err = String;
463
464 fn from_str(s: &str) -> Result<Self, Self::Err> {
465 match s.to_lowercase().as_str() {
466 "transfer" => Ok(TokenTransactionType::Transfer),
467 "mint" => Ok(TokenTransactionType::Mint),
468 "burn" => Ok(TokenTransactionType::Burn),
469 _ => Err(format!("Invalid token transaction type '{s}'")),
470 }
471 }
472}
473
474#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
475#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
476pub struct SparkInvoicePaymentDetails {
477 /// Represents the spark invoice description
478 pub description: Option<String>,
479 /// The raw spark invoice string
480 pub invoice: String,
481}
482
483#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
484#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
485pub struct SparkHtlcDetails {
486 /// The payment hash of the HTLC
487 pub payment_hash: String,
488 /// The preimage of the HTLC. Empty until receiver has released it.
489 pub preimage: Option<String>,
490 /// The expiry time of the HTLC as a unix timestamp in seconds
491 pub expiry_time: u64,
492 /// The HTLC status
493 pub status: SparkHtlcStatus,
494}
495
496#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
497#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
498pub enum SparkHtlcStatus {
499 /// The HTLC is waiting for the preimage to be shared by the receiver
500 WaitingForPreimage,
501 /// The HTLC preimage has been shared and the transfer can be or has been claimed by the receiver
502 PreimageShared,
503 /// The HTLC has been returned to the sender due to expiry
504 Returned,
505}
506
507impl fmt::Display for SparkHtlcStatus {
508 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509 match self {
510 SparkHtlcStatus::WaitingForPreimage => write!(f, "WaitingForPreimage"),
511 SparkHtlcStatus::PreimageShared => write!(f, "PreimageShared"),
512 SparkHtlcStatus::Returned => write!(f, "Returned"),
513 }
514 }
515}
516
517impl FromStr for SparkHtlcStatus {
518 type Err = String;
519
520 fn from_str(s: &str) -> Result<Self, Self::Err> {
521 match s {
522 "WaitingForPreimage" => Ok(SparkHtlcStatus::WaitingForPreimage),
523 "PreimageShared" => Ok(SparkHtlcStatus::PreimageShared),
524 "Returned" => Ok(SparkHtlcStatus::Returned),
525 _ => Err("Invalid Spark HTLC status".to_string()),
526 }
527 }
528}
529
530#[derive(Debug, Clone, Copy, PartialEq, Eq)]
531#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
532pub enum Network {
533 Mainnet,
534 Regtest,
535}
536
537impl std::fmt::Display for Network {
538 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
539 match self {
540 Network::Mainnet => write!(f, "Mainnet"),
541 Network::Regtest => write!(f, "Regtest"),
542 }
543 }
544}
545
546impl From<Network> for BitcoinNetwork {
547 fn from(network: Network) -> Self {
548 match network {
549 Network::Mainnet => BitcoinNetwork::Bitcoin,
550 Network::Regtest => BitcoinNetwork::Regtest,
551 }
552 }
553}
554
555impl From<Network> for breez_sdk_common::network::BitcoinNetwork {
556 fn from(network: Network) -> Self {
557 match network {
558 Network::Mainnet => breez_sdk_common::network::BitcoinNetwork::Bitcoin,
559 Network::Regtest => breez_sdk_common::network::BitcoinNetwork::Regtest,
560 }
561 }
562}
563
564impl From<Network> for bitcoin::Network {
565 fn from(network: Network) -> Self {
566 match network {
567 Network::Mainnet => bitcoin::Network::Bitcoin,
568 Network::Regtest => bitcoin::Network::Regtest,
569 }
570 }
571}
572
573impl FromStr for Network {
574 type Err = String;
575
576 fn from_str(s: &str) -> Result<Self, Self::Err> {
577 match s {
578 "mainnet" => Ok(Network::Mainnet),
579 "regtest" => Ok(Network::Regtest),
580 _ => Err("Invalid network".to_string()),
581 }
582 }
583}
584
585#[derive(Debug, Clone)]
586#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
587#[allow(clippy::struct_excessive_bools)]
588pub struct Config {
589 pub api_key: Option<String>,
590 pub network: Network,
591 pub sync_interval_secs: u32,
592
593 // The maximum fee that can be paid for a static deposit claim
594 // If not set then any fee is allowed
595 pub max_deposit_claim_fee: Option<MaxFee>,
596
597 /// Maximum instant (0-conf) static deposit claim fee, as basis points of the
598 /// deposit value (100 bps = 1%), capping the SSP spread for the instant
599 /// credit. Opt-in: while unset, no 0-conf claim is attempted. Small deposits,
600 /// whose spread is proportionally larger, fall through to the normal claim.
601 pub max_instant_deposit_claim_fee_bps: Option<u32>,
602
603 /// The domain used for receiving through lnurl-pay and lightning address.
604 pub lnurl_domain: Option<String>,
605
606 /// When this is set to `true` we will prefer to use spark payments over
607 /// lightning when sending and receiving. This has the benefit of lower fees
608 /// but is at the cost of privacy.
609 pub prefer_spark_over_lightning: bool,
610
611 /// Whether the data needed to exit a payment unilaterally, without the Spark
612 /// operators, is collected as funds arrive. Collection runs in the background,
613 /// and a sync waits for a collection pass before returning, so syncing is how
614 /// to make that happen at a moment of your choosing. A leaf the operators
615 /// cannot complete stays un-exitable until a later attempt succeeds.
616 ///
617 /// Leave this on unless bandwidth matters more than being able to recover funds
618 /// when the operators are unreachable. With it off, chains are only collected
619 /// when an exit is prepared, which needs the operators reachable at that
620 /// moment: a leaf cannot be exited without them until one is collected.
621 ///
622 /// Default value is true.
623 pub exit_chain_auto_fetch_enabled: bool,
624
625 /// A set of external input parsers that are used by [`BreezSdk::parse`](crate::sdk::BreezSdk::parse) when the input
626 /// is not recognized. See [`ExternalInputParser`] for more details on how to configure
627 /// external parsing.
628 pub external_input_parsers: Option<Vec<ExternalInputParser>>,
629 /// The SDK includes some default external input parsers
630 /// ([`DEFAULT_EXTERNAL_INPUT_PARSERS`]).
631 /// Set this to false in order to prevent their use.
632 pub use_default_external_input_parsers: bool,
633
634 /// Url to use for the real-time sync server. Defaults to the Breez real-time sync server.
635 pub real_time_sync_server_url: Option<String>,
636
637 /// Whether the Spark private mode is enabled by default.
638 ///
639 /// If set to true, the Spark private mode will be enabled on the first
640 /// initialization of the SDK. If set to false, no changes will be made
641 /// to the Spark private mode.
642 ///
643 /// This default is only auto-applied when `background_tasks_enabled` is
644 /// `true`. When `background_tasks_enabled` is `false`, the SDK does not
645 /// touch the Spark private mode on startup; call `update_user_settings`
646 /// with `spark_private_mode_enabled` set as needed on a one-time setup
647 /// pass.
648 pub private_enabled_default: bool,
649
650 /// Configuration for leaf optimization.
651 ///
652 /// Leaf optimization controls the denominations of leaves that are held in the wallet.
653 /// Fewer, bigger leaves allow for more funds to be exited unilaterally.
654 /// More leaves allow payments to be made without needing a swap, reducing payment latency.
655 pub leaf_optimization_config: LeafOptimizationConfig,
656
657 /// Configuration for token-output optimization.
658 ///
659 /// Token-output optimization controls automatic consolidation of a token's
660 /// available outputs. Keeping the output set small reduces transaction size,
661 /// while keeping enough distinct outputs preserves concurrency for parallel
662 /// sends.
663 pub token_optimization_config: TokenOptimizationConfig,
664
665 /// Configuration for automatic conversion of Bitcoin to stable tokens.
666 ///
667 /// When set, received sats will be automatically converted to the specified token
668 /// once the balance exceeds the threshold.
669 pub stable_balance_config: Option<StableBalanceConfig>,
670
671 /// Maximum number of concurrent transfer claims.
672 ///
673 /// Default is 4. Increase for server environments with high incoming payment volume.
674 pub max_concurrent_claims: u32,
675
676 /// Optional custom Spark environment configuration.
677 ///
678 /// When set, overrides the default Spark operator pool, service provider,
679 /// threshold, and token settings. Use this to connect to alternative Spark
680 /// deployments (e.g. dev/staging environments).
681 pub spark_config: Option<SparkConfig>,
682
683 /// Master switch for per-instance background services.
684 ///
685 /// When `true` (default), the SDK runs its standard background work:
686 /// periodic sync, lightning-address recovery, private-mode initialization,
687 /// the leaf and token-output optimizers, the Spark server-event
688 /// subscription, and the real-time sync client (when
689 /// `real_time_sync_server_url` is set).
690 ///
691 /// When `false`, **no background service is started**, regardless of any
692 /// other setting on this config. This is intended for multi-tenant server
693 /// deployments where the host application orchestrates sync and claims
694 /// explicitly and receives events via webhooks. Use
695 /// `default_server_config` to get this preset.
696 ///
697 /// Explicit operations (`sync_wallet`, `claim_deposit`,
698 /// `list_unclaimed_deposits`, `refund_deposit`,
699 /// `refund_pending_conversions`, leaf/token optimization, etc.) work
700 /// regardless of this flag.
701 ///
702 /// When `false`, the SDK rejects builds where fields whose backing
703 /// service is gated off are still in their active shape:
704 /// `stable_balance_config` must be `None`, `real_time_sync_server_url`
705 /// must be `None`, and `optimization_config.auto_enabled` must be `false`.
706 /// `default_server_config` already sets these compatible values.
707 pub background_tasks_enabled: bool,
708
709 /// Configuration for cross-chain sends via Orchestra and Boltz.
710 ///
711 /// `Some(_)` enables cross-chain sends (sats to USDT on external chains).
712 /// `None` (default) disables them entirely. Opt in by setting this to
713 /// [`CrossChainConfig::default`] (or a customized value): the providers
714 /// run background work (e.g. web sockets), so enabling is left to the
715 /// caller. Cross-chain sends are only supported on mainnet.
716 pub cross_chain_config: Option<CrossChainConfig>,
717}
718
719/// Configuration for cross-chain sends.
720///
721/// The presence of this struct on [`Config::cross_chain_config`] enables
722/// cross-chain providers; `None` disables them.
723#[derive(Debug, Clone, Default)]
724#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
725pub struct CrossChainConfig {
726 /// Default maximum slippage in basis points used when
727 /// [`PaymentRequest::CrossChain::max_slippage_bps`] is not set on the
728 /// prepare request. Must be in `10..=500`. Falls back to 100 bps (1%)
729 /// when this field is `None`.
730 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
731 pub default_slippage_bps: Option<u32>,
732 /// Default target-overpay pad in basis points applied to the user's
733 /// destination amount on `FeesExcluded` conversion sends. Bumps the
734 /// target upward before quoting so the recipient lands at or above the
735 /// requested amount despite provider slippage. Must be in `0..=500`.
736 /// Falls back to 15 bps when `None`.
737 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
738 pub default_target_overpay_bps: Option<u32>,
739}
740
741/// Configuration for leaf optimization.
742#[derive(Debug, Clone)]
743#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
744pub struct LeafOptimizationConfig {
745 /// Whether automatic leaf optimization is enabled.
746 ///
747 /// If set to true, the SDK will automatically optimize the leaf set when it changes.
748 /// Otherwise, the manual optimization API must be used to optimize the leaf set.
749 ///
750 /// Default value is true.
751 pub auto_enabled: bool,
752 /// The desired multiplicity for the leaf set.
753 ///
754 /// Setting this to 0 will optimize for maximizing unilateral exit.
755 /// Higher values will optimize for minimizing transfer swaps, with higher values
756 /// being more aggressive and allowing better TPS rates.
757 ///
758 /// For end-user wallets, values of 1-5 are recommended. Values above 5 are
759 /// intended for high-throughput server environments and are not recommended
760 /// for end-user wallets due to significantly higher unilateral exit costs.
761 ///
762 /// Default value is 1.
763 pub multiplicity: u8,
764}
765
766/// Configuration for token-output optimization.
767#[derive(Debug, Clone)]
768#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
769pub struct TokenOptimizationConfig {
770 /// Whether automatic token-output consolidation is enabled.
771 ///
772 /// If set to true, the SDK will periodically consolidate a token's outputs
773 /// once their count exceeds [`Self::min_outputs_threshold`]. Otherwise, no
774 /// automatic consolidation is performed.
775 ///
776 /// Default value is true.
777 pub auto_enabled: bool,
778 /// Number of token outputs to produce when token-output auto-consolidation
779 /// fires.
780 ///
781 /// Instead of collapsing a token's outputs into a single output (which
782 /// serializes subsequent payments), the SDK splits the consolidated balance
783 /// across this many outputs of roughly equal value. Higher values preserve
784 /// concurrency for parallel sends at the cost of a slightly larger output
785 /// set.
786 ///
787 /// Must be >= 1 and strictly less than [`Self::min_outputs_threshold`].
788 ///
789 /// Default value is 5.
790 pub target_output_count: u32,
791 /// Output count that triggers per-token auto-consolidation.
792 ///
793 /// Auto-consolidation triggers for a token when its available output count
794 /// strictly exceeds this threshold.
795 ///
796 /// Must be greater than 1.
797 ///
798 /// Default value is 50.
799 pub min_outputs_threshold: u32,
800}
801
802/// A stable token that can be used for automatic balance conversion.
803#[derive(Debug, Clone)]
804#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
805pub struct StableBalanceToken {
806 /// Integrator-defined display label for the token, e.g. "USD".
807 ///
808 /// This is a short, human-readable name set by the integrator for display purposes.
809 /// It is **not** a canonical Spark token ticker — it has no protocol-level meaning.
810 /// Labels must be unique within the [`StableBalanceConfig::tokens`] list.
811 pub label: String,
812
813 /// The full token identifier string used for conversions.
814 pub token_identifier: String,
815}
816
817/// Configuration for automatic conversion of Bitcoin to stable tokens.
818///
819/// When configured, the SDK automatically monitors the Bitcoin balance after each
820/// wallet sync. When the balance exceeds the configured threshold plus the reserved
821/// amount, the SDK automatically converts the excess balance (above the reserve)
822/// to the active stable token.
823///
824/// When the balance is held in a stable token, Bitcoin payments can still be sent.
825/// The SDK automatically detects when there's not enough Bitcoin balance to cover a
826/// payment and auto-populates the token-to-Bitcoin conversion options to facilitate
827/// the payment.
828///
829/// The active token can be changed at runtime via [`UpdateUserSettingsRequest`].
830#[derive(Debug, Clone)]
831#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
832pub struct StableBalanceConfig {
833 /// Available tokens that can be used for stable balance.
834 pub tokens: Vec<StableBalanceToken>,
835
836 /// The label of the token to activate by default.
837 ///
838 /// If `None`, stable balance starts deactivated. The user can activate it
839 /// at runtime via [`UpdateUserSettingsRequest`]. If a user setting is cached
840 /// locally, it takes precedence over this default.
841 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
842 pub default_active_label: Option<String>,
843
844 /// The minimum sats balance that triggers auto-conversion.
845 ///
846 /// If not provided, uses the minimum from conversion limits.
847 /// If provided but less than the conversion limit minimum, the limit minimum is used.
848 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
849 pub threshold_sats: Option<u64>,
850
851 /// Maximum slippage in basis points (1/100 of a percent).
852 ///
853 /// Defaults to 10 bps (0.1%) if not set.
854 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
855 pub max_slippage_bps: Option<u32>,
856}
857
858/// Specifies how to update the active stable balance token.
859#[derive(Debug, Clone)]
860#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
861pub enum StableBalanceActiveLabel {
862 /// Activate stable balance with the given label.
863 Set { label: String },
864 /// Deactivate stable balance.
865 Unset,
866}
867
868/// Configuration for a custom Spark environment.
869///
870/// When set on [`Config`], overrides the default Spark operator pool,
871/// service provider, threshold, and token settings. This allows connecting
872/// to alternative Spark deployments (e.g. dev/staging environments).
873#[derive(Debug, Clone)]
874#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
875pub struct SparkConfig {
876 /// Hex-encoded identifier of the coordinator operator.
877 pub coordinator_identifier: String,
878 /// The FROST signing threshold (e.g. 2 of 3).
879 pub threshold: u32,
880 /// The set of signing operators.
881 pub signing_operators: Vec<SparkSigningOperator>,
882 /// Service provider (SSP) configuration.
883 pub ssp_config: SparkSspConfig,
884 /// Expected bond amount in sats for token withdrawals.
885 pub expected_withdraw_bond_sats: u64,
886 /// Expected relative block locktime for token withdrawals.
887 pub expected_withdraw_relative_block_locktime: u64,
888 /// Cap on the inputs a single token transaction may spend. A send needing
889 /// more first consolidates the wallet's token outputs. Unset uses the SDK
890 /// default (500).
891 pub max_token_transaction_inputs: Option<u32>,
892}
893
894/// A Spark signing operator.
895#[derive(Debug, Clone)]
896#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
897pub struct SparkSigningOperator {
898 /// Sequential operator ID (0-indexed).
899 pub id: u32,
900 /// Hex-encoded 32-byte FROST identifier.
901 pub identifier: String,
902 /// gRPC address of the operator (e.g. `https://0.spark.lightspark.com`).
903 pub address: String,
904 /// Hex-encoded compressed public key of the operator.
905 pub identity_public_key: String,
906 /// Optional PEM-encoded CA certificate for TLS verification.
907 /// When set, the SDK uses this CA to verify the operator's TLS certificate
908 /// instead of the system/default roots. Useful for local development with
909 /// self-signed certificates.
910 pub ca_cert_pem: Option<String>,
911}
912
913/// Configuration for the Spark Service Provider (SSP).
914#[derive(Debug, Clone)]
915#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
916pub struct SparkSspConfig {
917 /// Base URL of the SSP GraphQL API.
918 pub base_url: String,
919 /// Hex-encoded compressed public key of the SSP.
920 pub identity_public_key: String,
921 /// Optional GraphQL schema endpoint path (e.g. "graphql/spark/rc").
922 /// Defaults to the hardcoded schema endpoint if not set.
923 pub schema_endpoint: Option<String>,
924}
925
926impl Config {
927 /// Validates the configuration.
928 ///
929 /// Returns an error if any configuration values are invalid.
930 pub fn validate(&self) -> Result<(), SdkError> {
931 if self.max_concurrent_claims == 0 {
932 return Err(SdkError::InvalidInput(
933 "max_concurrent_claims must be greater than 0".to_string(),
934 ));
935 }
936
937 if let Some(sb) = &self.stable_balance_config {
938 if sb.tokens.is_empty() {
939 return Err(SdkError::InvalidInput(
940 "tokens must not be empty".to_string(),
941 ));
942 }
943
944 let mut seen_labels = HashSet::new();
945 let mut seen_identifiers = HashSet::new();
946 for token in &sb.tokens {
947 if token.label.is_empty() {
948 return Err(SdkError::InvalidInput(
949 "token label must not be empty".to_string(),
950 ));
951 }
952 if token.token_identifier.is_empty() {
953 return Err(SdkError::InvalidInput(
954 "token_identifier must not be empty".to_string(),
955 ));
956 }
957 if !seen_labels.insert(&token.label) {
958 return Err(SdkError::InvalidInput(format!(
959 "tokens contains duplicate label: {}",
960 token.label
961 )));
962 }
963 if !seen_identifiers.insert(&token.token_identifier) {
964 return Err(SdkError::InvalidInput(format!(
965 "tokens contains duplicate token_identifier: {}",
966 token.token_identifier
967 )));
968 }
969 }
970
971 if let Some(bps) = sb.max_slippage_bps
972 && bps > 10000
973 {
974 return Err(SdkError::InvalidInput(
975 "max_slippage_bps must be <= 10000".to_string(),
976 ));
977 }
978
979 if let Some(default_label) = &sb.default_active_label
980 && !seen_labels.contains(default_label)
981 {
982 return Err(SdkError::InvalidInput(format!(
983 "default_active_label '{default_label}' not found in tokens list"
984 )));
985 }
986 }
987
988 let token_opt = &self.token_optimization_config;
989 if token_opt.min_outputs_threshold <= 1 {
990 return Err(SdkError::InvalidInput(
991 "token optimization minimum outputs threshold must be greater than 1".to_string(),
992 ));
993 }
994 if token_opt.target_output_count < 1 {
995 return Err(SdkError::InvalidInput(
996 "token optimization target output count must be at least 1".to_string(),
997 ));
998 }
999 if token_opt.target_output_count >= token_opt.min_outputs_threshold {
1000 return Err(SdkError::InvalidInput(
1001 "token optimization target output count must be less than the minimum outputs threshold".to_string(),
1002 ));
1003 }
1004
1005 if let Some(cc) = &self.cross_chain_config {
1006 if self.network != Network::Mainnet {
1007 return Err(SdkError::InvalidInput(format!(
1008 "Cross-chain sends are only available on Mainnet, not on {}.",
1009 self.network,
1010 )));
1011 }
1012 if let Some(bps) = cc.default_slippage_bps
1013 && !(crate::cross_chain::MIN_CROSS_CHAIN_SLIPPAGE_BPS
1014 ..=crate::cross_chain::MAX_CROSS_CHAIN_SLIPPAGE_BPS)
1015 .contains(&bps)
1016 {
1017 return Err(SdkError::InvalidInput(format!(
1018 "Default cross-chain slippage must be between {} and {} basis points, but got {bps}.",
1019 crate::cross_chain::MIN_CROSS_CHAIN_SLIPPAGE_BPS,
1020 crate::cross_chain::MAX_CROSS_CHAIN_SLIPPAGE_BPS,
1021 )));
1022 }
1023 if let Some(bps) = cc.default_target_overpay_bps
1024 && !(crate::cross_chain::MIN_TARGET_OVERPAY_BPS
1025 ..=crate::cross_chain::MAX_TARGET_OVERPAY_BPS)
1026 .contains(&bps)
1027 {
1028 return Err(SdkError::InvalidInput(format!(
1029 "Default cross-chain target-overpay must be between {} and {} basis points, but got {bps}.",
1030 crate::cross_chain::MIN_TARGET_OVERPAY_BPS,
1031 crate::cross_chain::MAX_TARGET_OVERPAY_BPS,
1032 )));
1033 }
1034 }
1035
1036 Ok(())
1037 }
1038
1039 pub(crate) fn get_all_external_input_parsers(&self) -> Vec<ExternalInputParser> {
1040 let mut external_input_parsers = Vec::new();
1041 if self.use_default_external_input_parsers {
1042 let default_parsers = DEFAULT_EXTERNAL_INPUT_PARSERS
1043 .iter()
1044 .map(|(id, regex, url)| ExternalInputParser {
1045 provider_id: (*id).to_string(),
1046 input_regex: (*regex).to_string(),
1047 parser_url: (*url).to_string(),
1048 })
1049 .collect::<Vec<_>>();
1050 external_input_parsers.extend(default_parsers);
1051 }
1052 external_input_parsers.extend(self.external_input_parsers.clone().unwrap_or_default());
1053
1054 external_input_parsers
1055 }
1056}
1057
1058#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1059#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1060pub enum MaxFee {
1061 // Fixed fee amount in sats
1062 Fixed { amount: u64 },
1063 // Relative fee rate in satoshis per vbyte
1064 Rate { sat_per_vbyte: u64 },
1065 // Fastest network recommended fee at the time of claim, with a leeway in satoshis per vbyte
1066 NetworkRecommended { leeway_sat_per_vbyte: u64 },
1067}
1068
1069impl MaxFee {
1070 pub(crate) async fn to_fee(&self, client: &dyn BitcoinChainService) -> Result<Fee, SdkError> {
1071 match self {
1072 MaxFee::Fixed { amount } => Ok(Fee::Fixed { amount: *amount }),
1073 MaxFee::Rate { sat_per_vbyte } => Ok(Fee::Rate {
1074 sat_per_vbyte: *sat_per_vbyte,
1075 }),
1076 MaxFee::NetworkRecommended {
1077 leeway_sat_per_vbyte,
1078 } => {
1079 let recommended_fees = client.recommended_fees().await?;
1080 let max_fee_rate = recommended_fees
1081 .fastest_fee
1082 .saturating_add(*leeway_sat_per_vbyte);
1083 Ok(Fee::Rate {
1084 sat_per_vbyte: max_fee_rate,
1085 })
1086 }
1087 }
1088 }
1089}
1090
1091#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1092#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1093pub enum Fee {
1094 // Fixed fee amount in sats
1095 Fixed { amount: u64 },
1096 // Relative fee rate in satoshis per vbyte
1097 Rate { sat_per_vbyte: u64 },
1098}
1099
1100impl Fee {
1101 pub fn to_sats(&self, vbytes: u64) -> u64 {
1102 match self {
1103 Fee::Fixed { amount } => *amount,
1104 Fee::Rate { sat_per_vbyte } => sat_per_vbyte.saturating_mul(vbytes),
1105 }
1106 }
1107}
1108
1109/// Why an instant (0-conf) claim was declined.
1110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1111#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1112pub enum InstantClaimDeclineReason {
1113 /// The SSP offered no 0-conf fulfillment plan for the deposit.
1114 NoPlan,
1115 /// The SSP spread exceeded the ceiling (`max_bps`). The instant claim can be
1116 /// retried with a higher ceiling. `quoted_bps` / `quoted_sats` are the spread
1117 /// the SSP quoted at the time.
1118 FeeExceeded {
1119 max_bps: u32,
1120 quoted_bps: u32,
1121 quoted_sats: u64,
1122 },
1123 /// The claim submission failed with an unknown outcome.
1124 SubmissionFailed,
1125}
1126
1127/// State of an instant (0-conf) claim attempt on a deposit.
1128#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1129#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1130pub enum InstantClaimStatus {
1131 /// The instant claim was declined. The deposit falls through to the normal
1132 /// claim once it matures; the background sync may re-attempt an instant claim
1133 /// only when the reason permits (see [`InstantClaimDeclineReason`]).
1134 Declined { reason: InstantClaimDeclineReason },
1135 /// An instant claim was submitted and is settling. The deposit must not be
1136 /// re-claimed (instant or normal) until the claim settles and it is reconciled
1137 /// out. Carries the SSP claim id.
1138 Submitted { claim_id: String },
1139}
1140
1141#[derive(Debug, Clone, Serialize, Deserialize)]
1142#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1143pub struct DepositInfo {
1144 /// Transaction id of the on-chain output the deposit came from.
1145 pub txid: String,
1146 /// Index of that output within its transaction.
1147 pub vout: u32,
1148 /// Deposit value in satoshis.
1149 pub amount_sats: u64,
1150 /// Whether the deposit has enough confirmations to be claimed.
1151 pub is_mature: bool,
1152 /// Raw refund transaction, once one has been created.
1153 pub refund_tx: Option<String>,
1154 /// Transaction id of the refund, once one has been created.
1155 pub refund_tx_id: Option<String>,
1156 /// Why the last claim attempt failed. Unset while none has failed.
1157 pub claim_error: Option<DepositClaimError>,
1158 /// Unset when no instant claim has been attempted.
1159 pub instant_claim_status: Option<InstantClaimStatus>,
1160}
1161
1162#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1163pub struct ClaimDepositRequest {
1164 pub txid: String,
1165 pub vout: u32,
1166 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1167 pub max_fee: Option<MaxFee>,
1168 /// Set to request an instant (0-conf) claim instead of waiting for the
1169 /// deposit to mature, bounding the SSP spread at this many basis points of
1170 /// the deposit value (100 bps = 1%). When set, the call takes the instant
1171 /// path and `max_fee` is ignored.
1172 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1173 pub max_instant_fee_bps: Option<u32>,
1174}
1175
1176#[derive(Debug, Clone, Serialize)]
1177#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1178pub struct ClaimDepositResponse {
1179 /// The settled claim payment. Present for a standard claim, which completes
1180 /// synchronously. Absent for an instant claim, whose transfer settles
1181 /// asynchronously: watch for the payment via events or `list_payments`.
1182 pub payment: Option<Payment>,
1183}
1184
1185#[derive(Debug, Clone, Serialize)]
1186#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1187pub struct RefundDepositRequest {
1188 pub txid: String,
1189 pub vout: u32,
1190 pub destination_address: String,
1191 pub fee: Fee,
1192}
1193
1194#[derive(Debug, Clone, Serialize)]
1195#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1196pub struct RefundDepositResponse {
1197 pub tx_id: String,
1198 pub tx_hex: String,
1199}
1200
1201#[derive(Debug, Clone, Serialize)]
1202#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1203pub struct ListUnclaimedDepositsRequest {}
1204
1205#[derive(Debug, Clone, Serialize)]
1206#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1207pub struct ListUnclaimedDepositsResponse {
1208 pub deposits: Vec<DepositInfo>,
1209}
1210
1211/// The available providers for buying Bitcoin
1212/// Request to buy Bitcoin using an external provider.
1213///
1214/// Each variant carries only the parameters relevant to that provider.
1215#[derive(Debug, Clone)]
1216#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1217pub enum BuyBitcoinRequest {
1218 /// `MoonPay`: Fiat-to-Bitcoin via credit card, Apple Pay, etc.
1219 /// Uses an on-chain deposit address.
1220 Moonpay {
1221 /// Lock the purchase to a specific amount in satoshis.
1222 locked_amount_sat: Option<u64>,
1223 /// Custom redirect URL after purchase completion.
1224 redirect_url: Option<String>,
1225 },
1226 /// `CashApp`: Pay via the Lightning Network.
1227 /// Generates a bolt11 invoice for the given amount and returns a
1228 /// `cash.app` deep link. Only available on mainnet.
1229 ///
1230 /// The amount is required. With an amountless invoice, Cash App only
1231 /// lets the payer fund from their existing Cash App BTC balance. With
1232 /// a fixed-amount invoice, Cash App opens up funding via fiat balance
1233 /// and debit card.
1234 CashApp {
1235 /// Amount in satoshis for the Lightning invoice. Must be non-zero.
1236 amount_sats: u64,
1237 },
1238}
1239
1240impl Default for BuyBitcoinRequest {
1241 fn default() -> Self {
1242 Self::Moonpay {
1243 locked_amount_sat: None,
1244 redirect_url: None,
1245 }
1246 }
1247}
1248
1249/// Response containing a URL to complete the Bitcoin purchase
1250#[derive(Debug, Clone, Serialize)]
1251#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1252pub struct BuyBitcoinResponse {
1253 /// The URL to open in a browser to complete the purchase
1254 pub url: String,
1255}
1256
1257/// Response from refunding pending conversions.
1258#[derive(Debug, Clone, Serialize, Default)]
1259#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1260pub struct RefundPendingConversionsResponse {
1261 /// Conversions successfully refunded this pass.
1262 pub refunded: u32,
1263 /// Conversions not clawed back this pass: held back by a safety window, or
1264 /// found to have executed after all. Only the former are retried.
1265 pub skipped: u32,
1266 /// Conversions whose clawback did not complete this pass (rejected or
1267 /// errored; funds not returned). The next pass will retry them.
1268 pub failed: u32,
1269}
1270
1271/// Request for a payment link that sends USDC/USDT to an external-chain
1272/// recipient, funded by Cash App over Lightning.
1273///
1274/// The user pays the returned URL, and the cross-chain provider delivers the
1275/// stablecoin to `address`. No funds move through the Spark wallet. Only
1276/// available on mainnet.
1277#[derive(Debug, Clone)]
1278#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1279pub struct PreparePaymentLinkRequest {
1280 /// Recipient address on the destination chain (e.g. an EVM `0x...` address).
1281 pub address: String,
1282 /// The destination route from calling `get_cross_chain_routes()` with the
1283 /// `CrossChainRouteFilter::PaymentLink` filter. Selects the destination chain
1284 /// + asset (e.g. USDC on Base).
1285 pub route: CrossChainRoutePair,
1286 /// Amount in the destination asset's base units, per the route's
1287 /// `decimals`. These routes deliver USD-pegged stablecoins, so at parity
1288 /// this is the USD value: `1_000_000` is 1 USDC (6 decimals), about $1.
1289 ///
1290 /// With the default fee policy the recipient receives this net amount.
1291 /// With `FeesIncluded` it is the amount the payer deposits.
1292 pub amount: u128,
1293 /// Whether fees are added on top of `amount` (`FeesExcluded`, the default)
1294 /// or deducted from it (`FeesIncluded`).
1295 pub fee_policy: Option<FeePolicy>,
1296 /// Maximum slippage tolerance in basis points. Falls back to the SDK
1297 /// default when unset.
1298 pub max_slippage_bps: Option<u32>,
1299}
1300
1301/// Response to a [`PreparePaymentLinkRequest`]. Mirrors `BuyBitcoinResponse`
1302/// (a payable `url`) plus the quote so the caller can display the expected
1303/// delivery and fees.
1304#[derive(Debug, Clone, Serialize)]
1305#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1306pub struct PreparePaymentLinkResponse {
1307 /// The URL to open in a browser; paying it delivers the stablecoin.
1308 pub url: String,
1309 /// Sats the payer deposits through the fiat rail.
1310 pub amount_sats: u64,
1311 /// Estimated amount delivered to the recipient, in `asset` base units.
1312 pub estimated_out: u128,
1313 /// The destination stablecoin symbol (e.g. `USDC`). `estimated_out` is
1314 /// denominated in it.
1315 pub asset: String,
1316 /// Provider service fee, in `service_fee_asset` base units.
1317 pub service_fee_amount: u128,
1318 /// Denomination of `service_fee_amount`. `None` means sats: Boltz
1319 /// denominates its fee in sats, Orchestra in the stablecoin.
1320 pub service_fee_asset: Option<String>,
1321 /// RFC3339 timestamp after which the quote is no longer valid.
1322 pub expires_at: String,
1323}
1324
1325impl std::fmt::Display for MaxFee {
1326 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1327 match self {
1328 MaxFee::Fixed { amount } => write!(f, "Fixed: {amount}"),
1329 MaxFee::Rate { sat_per_vbyte } => write!(f, "Rate: {sat_per_vbyte}"),
1330 MaxFee::NetworkRecommended {
1331 leeway_sat_per_vbyte,
1332 } => write!(f, "NetworkRecommended: {leeway_sat_per_vbyte}"),
1333 }
1334 }
1335}
1336
1337#[derive(Debug, Clone)]
1338#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1339pub struct Credentials {
1340 pub username: String,
1341 pub password: String,
1342}
1343
1344/// Request to get the balance of the wallet
1345#[derive(Debug, Clone)]
1346#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1347pub struct GetInfoRequest {
1348 /// When `Some(true)`, and `background_tasks_enabled` is `true`, the call
1349 /// waits for the initial Full sync to complete before returning.
1350 ///
1351 /// When `background_tasks_enabled` is `false`, setting this to `Some(true)`
1352 /// is rejected with an invalid-input error. There is no background sync to
1353 /// wait on; call `sync_wallet` explicitly first if you need fresh state.
1354 pub ensure_synced: Option<bool>,
1355}
1356
1357/// Response containing the balance of the wallet
1358#[derive(Debug, Clone, Serialize)]
1359#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1360pub struct GetInfoResponse {
1361 /// The identity public key of the wallet as a hex string
1362 pub identity_pubkey: String,
1363 /// The balance in satoshis
1364 pub balance_sats: u64,
1365 /// The balances of the tokens in the wallet keyed by the token identifier
1366 pub token_balances: HashMap<String, TokenBalance>,
1367}
1368
1369#[derive(Debug, Clone, Serialize, Deserialize)]
1370#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1371pub struct TokenBalance {
1372 pub balance: u128,
1373 pub token_metadata: TokenMetadata,
1374}
1375
1376#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1377#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1378pub struct TokenMetadata {
1379 pub identifier: String,
1380 /// Hex representation of the issuer public key
1381 pub issuer_public_key: String,
1382 pub name: String,
1383 pub ticker: String,
1384 /// Number of decimals the token uses
1385 pub decimals: u32,
1386 pub max_supply: u128,
1387 pub is_freezable: bool,
1388}
1389
1390/// Request to sync the wallet with the Spark network
1391#[derive(Debug, Clone)]
1392#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1393pub struct SyncWalletRequest {}
1394
1395/// Response from synchronizing the wallet
1396#[derive(Debug, Clone, Serialize)]
1397#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1398pub struct SyncWalletResponse {}
1399
1400#[derive(Debug, Clone, Serialize)]
1401#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1402pub enum ReceivePaymentMethod {
1403 SparkAddress,
1404 SparkInvoice {
1405 /// Amount to receive. Denominated in sats if token identifier is empty, otherwise in the token base units
1406 amount: Option<u128>,
1407 /// The presence of this field indicates that the payment is for a token
1408 /// If empty, it is a Bitcoin payment
1409 token_identifier: Option<String>,
1410 /// The expiry time of the invoice as a unix timestamp in seconds
1411 expiry_time: Option<u64>,
1412 /// A description to embed in the invoice.
1413 description: Option<String>,
1414 /// If set, the invoice may only be fulfilled by a payer with this public key
1415 sender_public_key: Option<String>,
1416 },
1417 BitcoinAddress {
1418 /// If true, rotate to a new deposit address. Previous ones remain valid.
1419 /// If false or absent, return the existing address (creating one if none
1420 /// exists yet).
1421 new_address: Option<bool>,
1422 },
1423 Bolt11Invoice {
1424 description: String,
1425 amount_sats: Option<u64>,
1426 /// The expiry of the invoice as a duration in seconds
1427 expiry_secs: Option<u32>,
1428 /// If set, creates a HODL invoice with this payment hash (hex-encoded).
1429 /// The payer's HTLC will be held until the preimage is provided via
1430 /// `claim_htlc_payment` or the HTLC expires.
1431 payment_hash: Option<String>,
1432 /// Spark identity public key that will receive the payment.
1433 /// If absent, the connected wallet's identity public key is used.
1434 receiver_identity_public_key: Option<String>,
1435 },
1436}
1437
1438#[derive(Debug, Clone, Serialize)]
1439#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1440pub enum SendPaymentMethod {
1441 BitcoinAddress {
1442 address: BitcoinAddressDetails,
1443 fee_quote: SendOnchainFeeQuote,
1444 },
1445 Bolt11Invoice {
1446 invoice_details: Bolt11InvoiceDetails,
1447 spark_transfer_fee_sats: Option<u64>,
1448 lightning_fee_sats: u64,
1449 }, // should be replaced with the parsed invoice
1450 SparkAddress {
1451 address: String,
1452 /// Fee to pay for the transaction
1453 /// Denominated in sats if token identifier is empty, otherwise in the token base units
1454 fee: u128,
1455 /// The presence of this field indicates that the payment is for a token
1456 /// If empty, it is a Bitcoin payment
1457 token_identifier: Option<String>,
1458 },
1459 SparkInvoice {
1460 spark_invoice_details: SparkInvoiceDetails,
1461 /// Fee to pay for the transaction
1462 /// Denominated in sats if token identifier is empty, otherwise in the token base units
1463 fee: u128,
1464 /// The presence of this field indicates that the payment is for a token
1465 /// If empty, it is a Bitcoin payment
1466 token_identifier: Option<String>,
1467 },
1468 /// A cross-chain send via a bridge/swap provider.
1469 CrossChainAddress {
1470 /// The route selected for this cross-chain send (includes provider, chain, asset).
1471 route: CrossChainRoutePair,
1472 /// Raw destination address (e.g. `0xabc...`).
1473 recipient_address: String,
1474 /// Amount routed to the provider, in the route's source-asset units
1475 /// (Boltz invoice sats; Orchestra deposit sats/token). On the
1476 /// token-conversion path (both `FeesIncluded` and `FeesExcluded`)
1477 /// the dispatcher overrides this with the wallet-side token debit
1478 /// when the source token and destination asset form a USD-stable pair.
1479 amount_in: u128,
1480 /// `amount_in` expressed in the cross-chain (destination) asset's
1481 /// base units, via the same rate the SDK used at prepare time.
1482 asset_amount_in: u128,
1483 /// Estimated recipient amount in cross-chain asset base units.
1484 estimated_out: u128,
1485 /// Prepare-time total user-visible fee in cross-chain asset base units.
1486 /// Covers provider spread + bridge/gas + DEX slippage. On the
1487 /// token-conversion path it also rolls in the LN routing budget; on
1488 /// the direct path that budget lives separately in
1489 /// `source_transfer_fee_sats`.
1490 fee_amount: u128,
1491 /// Provider's own service fee/spread in its native denomination.
1492 service_fee_amount: u128,
1493 /// Asset which service fee is denominated in. Unset means BTC sats.
1494 service_fee_asset: Option<String>,
1495 /// Sats budget for moving the amount in from the wallet to the provider.
1496 source_transfer_fee_sats: u64,
1497 /// Fee mode the prepare ran under; the send stage matches.
1498 fee_mode: CrossChainFeeMode,
1499 /// ISO8601 timestamp after which the quote is no longer valid.
1500 expires_at: String,
1501 /// Provider-internal state, produced when preparing and consumed
1502 /// when sending.
1503 provider_context: CrossChainProviderContext,
1504 },
1505}
1506
1507#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1508#[derive(Debug, Clone, Serialize, Deserialize)]
1509pub struct SendOnchainFeeQuote {
1510 pub id: String,
1511 pub expires_at: u64,
1512 pub speed_fast: SendOnchainSpeedFeeQuote,
1513 pub speed_medium: SendOnchainSpeedFeeQuote,
1514 pub speed_slow: SendOnchainSpeedFeeQuote,
1515}
1516
1517#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1518#[derive(Debug, Clone, Serialize, Deserialize)]
1519pub struct SendOnchainSpeedFeeQuote {
1520 pub user_fee_sat: u64,
1521 pub l1_broadcast_fee_sat: u64,
1522}
1523
1524impl SendOnchainSpeedFeeQuote {
1525 pub fn total_fee_sat(&self) -> u64 {
1526 self.user_fee_sat.saturating_add(self.l1_broadcast_fee_sat)
1527 }
1528}
1529
1530#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1531pub struct ReceivePaymentRequest {
1532 pub payment_method: ReceivePaymentMethod,
1533}
1534
1535#[derive(Debug, Clone, Serialize)]
1536#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1537pub struct ReceivePaymentResponse {
1538 pub payment_request: String,
1539 /// Fee to pay to receive the payment
1540 /// Denominated in sats or token base units
1541 pub fee: u128,
1542}
1543
1544#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1545pub struct PrepareLnurlPayRequest {
1546 /// The amount to send. Denominated in satoshis, or in token base units
1547 /// when `token_identifier` is set.
1548 pub amount: u128,
1549 pub pay_request: LnurlPayRequestDetails,
1550 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1551 pub comment: Option<String>,
1552 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1553 pub validate_success_action_url: Option<bool>,
1554 /// The token identifier when sending a token amount with conversion.
1555 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1556 pub token_identifier: Option<String>,
1557 /// If provided, the payment will include a token conversion step before sending the payment
1558 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1559 pub conversion_options: Option<ConversionOptions>,
1560 /// How fees are handled. See [`FeePolicy`]. Defaults to `FeesExcluded`.
1561 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1562 pub fee_policy: Option<FeePolicy>,
1563}
1564
1565#[derive(Debug, Clone)]
1566#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1567pub struct PrepareLnurlPayResponse {
1568 /// The amount for the payment, always denominated in sats, even when a
1569 /// `token_identifier` and conversion are present.
1570 /// When a conversion is present, the token input amount is available in
1571 /// `conversion_estimate.amount_in`.
1572 pub amount_sats: u64,
1573 pub comment: Option<String>,
1574 pub pay_request: LnurlPayRequestDetails,
1575 /// The fee in satoshis. For `FeesIncluded` operations, this represents the total fee
1576 /// (including potential overpayment).
1577 pub fee_sats: u64,
1578 pub invoice_details: Bolt11InvoiceDetails,
1579 pub success_action: Option<SuccessAction>,
1580 /// When set, the payment will include a token conversion step before sending the payment
1581 pub conversion_estimate: Option<ConversionEstimate>,
1582 /// The fee policy actually applied. May differ from the request — e.g.,
1583 /// LNURL sends with `token_identifier` set + conversion are always
1584 /// `FeesIncluded` (explicit `FeesExcluded` is rejected).
1585 pub fee_policy: FeePolicy,
1586}
1587
1588#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1589pub struct LnurlPayRequest {
1590 pub prepare_response: PrepareLnurlPayResponse,
1591 /// If set, providing the same idempotency key for multiple requests will ensure that only one
1592 /// payment is made. If an idempotency key is re-used, the same payment will be returned.
1593 /// The idempotency key must be a valid UUID.
1594 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1595 pub idempotency_key: Option<String>,
1596}
1597
1598#[derive(Debug, Serialize)]
1599#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1600pub struct LnurlPayResponse {
1601 pub payment: Payment,
1602 pub success_action: Option<SuccessActionProcessed>,
1603}
1604
1605#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1606pub struct BuildUnsignedLnurlPayPackageRequest {
1607 pub prepare_response: PrepareLnurlPayResponse,
1608}
1609
1610#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1611pub struct PublishSignedLnurlPayPackageRequest {
1612 pub signed_package: SignedTransferPackage,
1613}
1614
1615#[allow(clippy::large_enum_variant)]
1616#[derive(Debug)]
1617#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1618pub enum PublishSignedLnurlPayResponse {
1619 SwapCompleted,
1620 PaymentSent { response: LnurlPayResponse },
1621}
1622
1623#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1624pub struct LnurlWithdrawRequest {
1625 /// The amount to withdraw in satoshis
1626 /// Must be within the min and max withdrawable limits
1627 pub amount_sats: u64,
1628 pub withdraw_request: LnurlWithdrawRequestDetails,
1629 /// If set, the function will return the payment if it is still pending after this
1630 /// number of seconds. If unset, the function will return immediately after
1631 /// initiating the LNURL withdraw.
1632 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1633 pub completion_timeout_secs: Option<u32>,
1634}
1635
1636#[derive(Debug, Serialize)]
1637#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1638pub struct LnurlWithdrawResponse {
1639 /// The Lightning invoice generated for the LNURL withdraw
1640 pub payment_request: String,
1641 pub payment: Option<Payment>,
1642}
1643
1644/// Represents the payment LNURL info
1645#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1646#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1647pub struct LnurlPayInfo {
1648 pub ln_address: Option<String>,
1649 pub comment: Option<String>,
1650 pub domain: Option<String>,
1651 pub metadata: Option<String>,
1652 pub processed_success_action: Option<SuccessActionProcessed>,
1653 pub raw_success_action: Option<SuccessAction>,
1654}
1655
1656/// Represents the withdraw LNURL info
1657#[derive(Clone, Debug, Deserialize, Serialize)]
1658#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1659pub struct LnurlWithdrawInfo {
1660 pub withdraw_url: String,
1661}
1662
1663impl LnurlPayInfo {
1664 pub fn extract_description(&self) -> Option<String> {
1665 let Some(metadata) = &self.metadata else {
1666 return None;
1667 };
1668
1669 let Ok(metadata) = serde_json::from_str::<Vec<Vec<Value>>>(metadata) else {
1670 return None;
1671 };
1672
1673 for arr in metadata {
1674 if arr.len() != 2 {
1675 continue;
1676 }
1677 if let (Some(key), Some(value)) = (arr[0].as_str(), arr[1].as_str())
1678 && key == "text/plain"
1679 {
1680 return Some(value.to_string());
1681 }
1682 }
1683
1684 None
1685 }
1686}
1687
1688/// Specifies how fees are handled in a payment.
1689///
1690/// "Fees" are the wallet's sender-paid fees (Lightning routing, on-chain,
1691/// Spark transfer). They do not include provider spreads or destination-chain
1692/// costs on cross-chain routes; those are reported separately via
1693/// `estimated_out` on the prepare response and are not deterministic.
1694/// `FeePolicy` only controls the wallet's spend accounting.
1695#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
1696#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1697pub enum FeePolicy {
1698 /// Fees are added on top of `amount`. Wallet's total spend is
1699 /// `amount + fees`. For direct sat sends, the recipient receives exactly
1700 /// `amount`. Default.
1701 #[default]
1702 FeesExcluded,
1703 /// Fees are deducted from `amount`. Wallet's total spend is `amount`.
1704 /// Use this to drain a balance — pass `amount = balance` and the wallet
1705 /// spends exactly that.
1706 FeesIncluded,
1707}
1708
1709#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1710#[derive(Debug, Clone, Serialize, Deserialize)]
1711pub enum OnchainConfirmationSpeed {
1712 Fast,
1713 Medium,
1714 Slow,
1715}
1716
1717/// The payment destination. Either a raw string (bolt11, spark address, BIP-21,
1718/// cross-chain URI, etc.) that is parsed internally, or a structured
1719/// cross-chain destination with explicit chain + asset selection.
1720#[derive(Debug, Clone, Serialize)]
1721#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1722pub enum PaymentRequest {
1723 /// Unparsed user input string (bolt11, spark address, BIP-21, cross-chain URI, etc.)
1724 Input { input: String },
1725 /// Cross-chain send with a selected route from `get_cross_chain_routes()`.
1726 /// Amount comes from `PrepareSendPaymentRequest.amount`, not here.
1727 CrossChain {
1728 address: String,
1729 route: CrossChainRoutePair,
1730 /// Maximum slippage tolerance in basis points (1/100 of a percent)
1731 /// for the cross-chain quote. Must be in `10..=500`. Falls back to
1732 /// [`Config::default_slippage_bps`] when `None`, which itself
1733 /// defaults to 100 bps (1%) when unset.
1734 max_slippage_bps: Option<u32>,
1735 /// Target-overpay pad in basis points applied on `FeesExcluded`
1736 /// conversion sends. Inflates the destination target before quoting
1737 /// so the recipient lands at or above the user's requested amount
1738 /// despite provider slippage. Must be in `0..=500`. Falls back to
1739 /// [`CrossChainConfig::default_target_overpay_bps`] when `None`,
1740 /// which itself defaults to 15 bps.
1741 target_overpay_bps: Option<u32>,
1742 },
1743}
1744
1745#[allow(clippy::large_enum_variant)]
1746#[derive(Debug, Clone, Serialize, Deserialize)]
1747#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1748pub enum UnsignedTransferPackage {
1749 Swap {
1750 prepare_transfer: crate::signer::ExternalPrepareTransferRequest,
1751 target_amounts: Vec<u64>,
1752 amount_sat: u64,
1753 fee_sat: u64,
1754 },
1755 Transfer {
1756 prepare_transfer: crate::signer::ExternalPrepareTransferRequest,
1757 amount_sat: u64,
1758 fee_sat: u64,
1759 target: TransferTarget,
1760 },
1761 Token {
1762 prepare_token_transaction: crate::signer::ExternalPrepareTokenTransactionRequest,
1763 token_context: Vec<u8>,
1764 token_identifier: String,
1765 amount: u128,
1766 fee: u128,
1767 /// When set, this package re-shapes the wallet's token outputs instead of
1768 /// sending a payment. Publishing it returns `SwapCompleted`: rebuild the
1769 /// original send from the same prepare response and submit again.
1770 is_swap: bool,
1771 },
1772 /// One token transaction paying several recipients. Publishing it returns
1773 /// `PaymentsSent` with one payment per recipient.
1774 TokenBatch {
1775 prepare_token_transaction: crate::signer::ExternalPrepareTokenTransactionRequest,
1776 token_context: Vec<u8>,
1777 /// What the batch debits, per token. A batch spanning tokens has no
1778 /// single amount to report.
1779 totals: Vec<BatchTotal>,
1780 /// When set, this package re-shapes the wallet's token outputs instead of
1781 /// sending a payment. Publishing it returns `SwapCompleted`: rebuild the
1782 /// original send from the same prepare response and submit again.
1783 is_swap: bool,
1784 },
1785}
1786
1787#[derive(Debug, Clone, Serialize, Deserialize)]
1788#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1789pub enum TransferTarget {
1790 Spark {
1791 address: String,
1792 spark_invoice: Option<String>,
1793 },
1794 Lightning {
1795 bolt11: String,
1796 lnurl_pay: Option<LnurlPayContext>,
1797 fee_policy: FeePolicy,
1798 completion_timeout_secs: Option<u32>,
1799 },
1800 CoopExit {
1801 address: String,
1802 fee_quote: SendOnchainFeeQuote,
1803 confirmation_speed: OnchainConfirmationSpeed,
1804 },
1805}
1806
1807#[derive(Debug, Clone, Serialize, Deserialize)]
1808#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1809pub struct LnurlPayContext {
1810 pub pay_request: LnurlPayRequestDetails,
1811 pub comment: Option<String>,
1812 pub success_action: Option<SuccessAction>,
1813}
1814
1815#[derive(Debug, Clone, Serialize, Deserialize)]
1816#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1817pub struct SignedTransferPackage {
1818 pub unsigned: UnsignedTransferPackage,
1819 pub signature: TransferSignature,
1820}
1821
1822#[derive(Debug, Clone, Serialize, Deserialize)]
1823#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1824pub enum TransferSignature {
1825 Transfer {
1826 signed: crate::signer::ExternalPreparedTransfer,
1827 },
1828 Token {
1829 signed: crate::signer::ExternalPreparedTokenTransaction,
1830 },
1831}
1832
1833#[derive(Debug, Clone)]
1834#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1835pub enum BuildTransferPackageOptions {
1836 BitcoinAddress {
1837 confirmation_speed: OnchainConfirmationSpeed,
1838 },
1839 Bolt11Invoice {
1840 prefer_spark: bool,
1841
1842 /// If set, publishing the package waits up to this many seconds for the
1843 /// payment to complete before returning it while still pending. If unset,
1844 /// publishing returns immediately after initiating the payment.
1845 completion_timeout_secs: Option<u32>,
1846 },
1847}
1848
1849#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1850pub struct BuildUnsignedTransferPackageRequest {
1851 pub prepare_response: PrepareSendPaymentResponse,
1852 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1853 pub options: Option<BuildTransferPackageOptions>,
1854}
1855
1856#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1857pub struct PrepareSendPaymentRequest {
1858 pub payment_request: PaymentRequest,
1859 /// The amount to send.
1860 /// Optional for payment requests with embedded amounts (e.g., Spark/Bolt11 invoices with amounts).
1861 /// Required for Spark addresses, Bitcoin addresses, and amountless invoices.
1862 /// Denominated in satoshis for Bitcoin payments, or token base units for token payments.
1863 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1864 pub amount: Option<u128>,
1865 /// Optional token identifier for token payments.
1866 /// Absence indicates that the payment is a Bitcoin payment.
1867 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1868 pub token_identifier: Option<String>,
1869 /// If provided, the payment will include a conversion step before sending the payment
1870 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1871 pub conversion_options: Option<ConversionOptions>,
1872 /// How fees are handled. See [`FeePolicy`]. Defaults to `FeesExcluded`.
1873 ///
1874 /// Ignored on cross-chain AMM-conversion sends (whether the conversion was
1875 /// explicitly requested or auto-injected by stable balance) — fees come
1876 /// out of the converted sats. Bolt11 and Bitcoin AMM-conversion sends
1877 /// still respect this field by sizing the conversion to cover fees. The
1878 /// prepare response's `fee_policy` reflects what was actually applied.
1879 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1880 pub fee_policy: Option<FeePolicy>,
1881}
1882
1883#[derive(Debug, Clone, Serialize)]
1884#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1885pub struct PrepareSendPaymentResponse {
1886 pub payment_method: SendPaymentMethod,
1887 /// The amount to be sent, denominated in satoshis for Bitcoin payments
1888 /// (including token-to-Bitcoin conversions), or token base units for token payments.
1889 /// When a conversion is present, the input amount is in
1890 /// `conversion_estimate.amount_in`.
1891 pub amount: u128,
1892 /// Optional token identifier for token payments.
1893 /// Absence indicates that the payment is a Bitcoin payment.
1894 pub token_identifier: Option<String>,
1895 /// When set, the payment will include a conversion step before sending the payment
1896 pub conversion_estimate: Option<ConversionEstimate>,
1897 /// The fee policy actually applied. May differ from the request — e.g.,
1898 /// cross-chain AMM-conversion sends are always `FeesIncluded`.
1899 pub fee_policy: FeePolicy,
1900}
1901
1902#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1903pub enum SendPaymentOptions {
1904 BitcoinAddress {
1905 /// Confirmation speed for the on-chain transaction.
1906 confirmation_speed: OnchainConfirmationSpeed,
1907 },
1908 Bolt11Invoice {
1909 prefer_spark: bool,
1910
1911 /// If set, the function will return the payment if it is still pending after this
1912 /// number of seconds. If unset, the function will return immediately after initiating the payment.
1913 completion_timeout_secs: Option<u32>,
1914 },
1915 SparkAddress {
1916 /// Can only be provided for Bitcoin payments. If set, a Spark HTLC transfer will be created.
1917 /// The receiver will need to provide the preimage to claim it.
1918 htlc_options: Option<SparkHtlcOptions>,
1919 },
1920}
1921
1922#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1923pub struct SparkHtlcOptions {
1924 /// The payment hash of the HTLC. The receiver will need to provide the associated preimage to claim it.
1925 pub payment_hash: String,
1926 /// The duration of the HTLC in seconds.
1927 /// After this time, the HTLC will be returned.
1928 pub expiry_duration_secs: u64,
1929}
1930
1931#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1932pub struct SendPaymentRequest {
1933 pub prepare_response: PrepareSendPaymentResponse,
1934 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1935 pub options: Option<SendPaymentOptions>,
1936 /// The optional idempotency key for all Spark based transfers (excludes token payments
1937 /// and cross-chain sends).
1938 /// If set, providing the same idempotency key for multiple requests will ensure that only one
1939 /// payment is made. If an idempotency key is re-used, the same payment will be returned.
1940 /// The idempotency key must be a valid UUID.
1941 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1942 pub idempotency_key: Option<String>,
1943}
1944
1945/// A single payee in a batch send.
1946#[derive(Debug, Clone)]
1947#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1948pub struct BatchRecipient {
1949 /// Spark address or Spark invoice identifying the payee.
1950 pub payment_request: String,
1951 /// Amount to send, in the base units of the asset being sent. Required
1952 /// unless `payment_request` is an invoice that carries its own amount.
1953 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1954 pub amount: Option<u128>,
1955 /// Token to send. Unset means sats, which a batch cannot send yet, so a
1956 /// plain address needs this set. An invoice that names a token does not.
1957 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1958 pub token_identifier: Option<String>,
1959}
1960
1961#[derive(Debug, Clone)]
1962#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1963pub struct PrepareSendBatchRequest {
1964 /// The payees, all paid by one transaction. They may span several tokens,
1965 /// and may mix Spark addresses with Spark invoices. Once a Spark invoice is
1966 /// among them, every recipient must be paid in the same token.
1967 pub recipients: Vec<BatchRecipient>,
1968}
1969
1970/// Where a batch recipient is paid, once prepare has decoded its payment request.
1971#[derive(Debug, Clone, Serialize, Deserialize)]
1972#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1973pub enum BatchDestination {
1974 SparkAddress {
1975 address: String,
1976 },
1977 SparkInvoice {
1978 invoice_details: SparkInvoiceDetails,
1979 },
1980}
1981
1982/// A recipient after prepare has resolved the asset and amount it is owed.
1983#[derive(Debug, Clone, Serialize, Deserialize)]
1984#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1985pub struct ResolvedBatchRecipient {
1986 pub destination: BatchDestination,
1987 /// Amount in the base units of the asset this recipient is paid in.
1988 pub amount: u128,
1989 /// The token this recipient is paid in. Unset means sats, which a batch
1990 /// cannot send yet.
1991 pub token_identifier: Option<String>,
1992}
1993
1994/// What a batch debits for one asset.
1995#[derive(Debug, Clone, Serialize, Deserialize)]
1996#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1997pub struct BatchTotal {
1998 /// The token debited. Unset means sats, which a batch cannot send yet.
1999 pub token_identifier: Option<String>,
2000 /// Amount in the asset's base units.
2001 pub amount: u128,
2002}
2003
2004#[derive(Debug, Clone, Serialize, Deserialize)]
2005#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2006pub struct PrepareSendBatchResponse {
2007 /// The payees in the order they were requested, which is the order their
2008 /// payments come back in.
2009 pub recipients: Vec<ResolvedBatchRecipient>,
2010 /// What the batch debits, one entry per distinct asset.
2011 pub totals: Vec<BatchTotal>,
2012}
2013
2014#[derive(Debug, Clone)]
2015#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2016pub struct SendBatchRequest {
2017 pub prepare_response: PrepareSendBatchResponse,
2018}
2019
2020#[derive(Debug, Clone)]
2021#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2022pub struct SendBatchResponse {
2023 /// One payment per recipient, in recipient order, all sharing a transaction
2024 /// hash.
2025 pub payments: Vec<Payment>,
2026}
2027
2028#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2029pub struct BuildUnsignedBatchPackageRequest {
2030 pub prepare_response: PrepareSendBatchResponse,
2031}
2032
2033#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2034pub struct PublishSignedTransferPackageRequest {
2035 pub signed_package: SignedTransferPackage,
2036}
2037
2038#[allow(clippy::large_enum_variant)]
2039#[derive(Debug, Clone)]
2040#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2041pub enum PublishSignedTransferPackageResponse {
2042 SwapCompleted,
2043 PaymentSent {
2044 payment: Payment,
2045 },
2046 /// Returned for a batch package: one payment per recipient, in recipient
2047 /// order.
2048 PaymentsSent {
2049 payments: Vec<Payment>,
2050 },
2051}
2052
2053#[derive(Debug, Clone, Serialize)]
2054#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2055pub struct SendPaymentResponse {
2056 pub payment: Payment,
2057}
2058
2059#[derive(Debug, Clone)]
2060#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2061pub enum PaymentDetailsFilter {
2062 Spark {
2063 /// Filter specific Spark HTLC statuses
2064 htlc_status: Option<Vec<SparkHtlcStatus>>,
2065 /// Filter conversion payments with refund information
2066 conversion_refund_needed: Option<bool>,
2067 },
2068 Token {
2069 /// Filter conversion payments with refund information
2070 conversion_refund_needed: Option<bool>,
2071 /// Filter by transaction hash
2072 tx_hash: Option<String>,
2073 /// Filter by transaction type
2074 tx_type: Option<TokenTransactionType>,
2075 },
2076 Lightning {
2077 /// Filter specific Spark HTLC statuses
2078 htlc_status: Option<Vec<SparkHtlcStatus>>,
2079 },
2080}
2081
2082/// Request to list payments with optional filters and pagination
2083#[derive(Debug, Clone, Default)]
2084#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2085pub struct ListPaymentsRequest {
2086 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2087 pub type_filter: Option<Vec<PaymentType>>,
2088 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2089 pub status_filter: Option<Vec<PaymentStatus>>,
2090 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2091 pub asset_filter: Option<AssetFilter>,
2092 /// Only include payments matching at least one of these payment details filters
2093 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2094 pub payment_details_filter: Option<Vec<PaymentDetailsFilter>>,
2095 /// Only include payments created after this timestamp (inclusive)
2096 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2097 pub from_timestamp: Option<u64>,
2098 /// Only include payments created before this timestamp (exclusive)
2099 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2100 pub to_timestamp: Option<u64>,
2101 /// Number of records to skip
2102 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2103 pub offset: Option<u32>,
2104 /// Maximum number of records to return
2105 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2106 pub limit: Option<u32>,
2107 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2108 pub sort_ascending: Option<bool>,
2109}
2110
2111/// A field of [`ListPaymentsRequest`] when listing payments filtered by asset
2112#[derive(Debug, Clone)]
2113#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2114pub enum AssetFilter {
2115 Bitcoin,
2116 Token {
2117 /// Optional token identifier to filter by
2118 token_identifier: Option<String>,
2119 },
2120}
2121
2122impl FromStr for AssetFilter {
2123 type Err = String;
2124
2125 fn from_str(s: &str) -> Result<Self, Self::Err> {
2126 Ok(match s.to_lowercase().as_str() {
2127 "bitcoin" => AssetFilter::Bitcoin,
2128 "token" => AssetFilter::Token {
2129 token_identifier: None,
2130 },
2131 str if str.starts_with("token:") => AssetFilter::Token {
2132 token_identifier: Some(
2133 str.split_once(':')
2134 .ok_or(format!("Invalid asset filter '{s}'"))?
2135 .1
2136 .to_string(),
2137 ),
2138 },
2139 _ => return Err(format!("Invalid asset filter '{s}'")),
2140 })
2141 }
2142}
2143
2144/// Response from listing payments
2145#[derive(Debug, Clone, Serialize)]
2146#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2147pub struct ListPaymentsResponse {
2148 /// The list of payments
2149 pub payments: Vec<Payment>,
2150}
2151
2152#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2153pub struct GetPaymentRequest {
2154 pub payment_id: String,
2155}
2156
2157#[derive(Debug, Clone, Serialize)]
2158#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2159pub struct GetPaymentResponse {
2160 pub payment: Payment,
2161}
2162
2163#[cfg_attr(feature = "uniffi", uniffi::export(callback_interface))]
2164pub trait Logger: Send + Sync {
2165 fn log(&self, l: LogEntry);
2166}
2167
2168#[derive(Debug, Clone, Serialize)]
2169#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2170pub struct LogEntry {
2171 pub line: String,
2172 pub level: String,
2173}
2174
2175#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2176#[derive(Debug, Clone, Serialize, Deserialize)]
2177pub struct CheckLightningAddressRequest {
2178 pub username: String,
2179}
2180
2181#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2182#[derive(Debug, Clone, Serialize, Deserialize)]
2183pub struct RegisterLightningAddressRequest {
2184 pub username: String,
2185 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2186 pub description: Option<String>,
2187}
2188
2189/// Authorization from the current owner granting a specific new owner the
2190/// right to take over a username. Produced by
2191/// [`BreezSdk::authorize_lightning_address_transfer`] and handed to the new
2192/// owner, who passes it to [`BreezSdk::claim_lightning_address_transfer`]. It
2193/// fully describes the transfer, so the new owner needs nothing else to claim.
2194#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2195#[derive(Debug, Clone, Serialize, Deserialize)]
2196pub struct TransferAuthorization {
2197 /// The username being handed over.
2198 pub username: String,
2199 /// The current owner's public key.
2200 pub pubkey: String,
2201 /// The current owner's signature authorizing the transfer.
2202 pub signature: String,
2203 /// The lightning-address domain the authorization is for, taken from the
2204 /// address being handed over. The signed message names this domain, so an
2205 /// authorization made for one server does not verify at another.
2206 pub domain: String,
2207 /// When the authorization was produced, in seconds since the Unix epoch.
2208 /// Covered by the signature, and valid for 10 minutes: the transferee has
2209 /// to claim within that window or the current owner authorizes again.
2210 pub timestamp: u64,
2211}
2212
2213/// Request for [`BreezSdk::authorize_lightning_address_transfer`]. Called by
2214/// the *current owner* to authorize handing their registered username over to
2215/// `transferee_pubkey`.
2216#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2217#[derive(Debug, Clone, Serialize, Deserialize)]
2218pub struct AuthorizeTransferRequest {
2219 /// The new owner's identity public key.
2220 pub transferee_pubkey: String,
2221}
2222
2223/// Request for [`BreezSdk::claim_lightning_address_transfer`]. Called by the
2224/// *new owner* to complete the takeover using the authorization produced by
2225/// the current owner.
2226#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2227#[derive(Debug, Clone, Serialize, Deserialize)]
2228pub struct ClaimTransferRequest {
2229 /// Authorization produced by the current owner via
2230 /// [`BreezSdk::authorize_lightning_address_transfer`].
2231 pub authorization: TransferAuthorization,
2232 /// Description for the address. Defaults to `"Pay to {username}@{domain}"`.
2233 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2234 pub description: Option<String>,
2235}
2236
2237#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2238#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
2239pub struct LnurlInfo {
2240 pub url: String,
2241 pub bech32: String,
2242}
2243
2244impl LnurlInfo {
2245 pub fn new(url: String) -> Self {
2246 let bech32 =
2247 breez_sdk_common::lnurl::encode_lnurl_to_bech32(&url).unwrap_or_else(|_| url.clone());
2248 Self { url, bech32 }
2249 }
2250}
2251
2252#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2253#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
2254pub struct LightningAddressInfo {
2255 pub description: String,
2256 pub lightning_address: String,
2257 pub lnurl: LnurlInfo,
2258 pub username: String,
2259}
2260
2261impl From<RecoverLnurlPayResponse> for LightningAddressInfo {
2262 fn from(resp: RecoverLnurlPayResponse) -> Self {
2263 Self {
2264 description: resp.description,
2265 lightning_address: resp.lightning_address,
2266 lnurl: LnurlInfo::new(resp.lnurl),
2267 username: resp.username,
2268 }
2269 }
2270}
2271
2272/// Response from listing fiat currencies
2273#[derive(Debug, Clone, Serialize)]
2274#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2275pub struct ListFiatCurrenciesResponse {
2276 /// The list of fiat currencies
2277 pub currencies: Vec<FiatCurrency>,
2278}
2279
2280/// Response from listing fiat rates
2281#[derive(Debug, Clone, Serialize)]
2282#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2283pub struct ListFiatRatesResponse {
2284 /// The list of fiat rates
2285 pub rates: Vec<Rate>,
2286}
2287
2288/// The operational status of a Spark service.
2289#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
2290#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2291pub enum ServiceStatus {
2292 /// Service is fully operational.
2293 Operational,
2294 /// Service is experiencing degraded performance.
2295 Degraded,
2296 /// Service is partially unavailable.
2297 Partial,
2298 /// Service status is unknown.
2299 Unknown,
2300 /// Service is experiencing a major outage.
2301 Major,
2302}
2303
2304/// The status of the Spark network services relevant to the SDK.
2305#[derive(Debug, Clone, Serialize)]
2306#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2307pub struct SparkStatus {
2308 /// The worst status across all relevant services.
2309 pub status: ServiceStatus,
2310 /// The last time the status was updated, as a unix timestamp in seconds.
2311 pub last_updated: u64,
2312}
2313
2314pub(crate) enum WaitForPaymentIdentifier {
2315 PaymentId(String),
2316 LightningReceive { invoice: String, ssp_id: String },
2317}
2318
2319#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2320pub struct GetTokensMetadataRequest {
2321 pub token_identifiers: Vec<String>,
2322}
2323
2324#[derive(Debug, Clone, Serialize)]
2325#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2326pub struct GetTokensMetadataResponse {
2327 pub tokens_metadata: Vec<TokenMetadata>,
2328}
2329
2330#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2331pub struct SignMessageRequest {
2332 pub message: String,
2333 /// If true, the signature will be encoded in compact format instead of DER format
2334 pub compact: bool,
2335}
2336
2337#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2338pub struct SignMessageResponse {
2339 pub pubkey: String,
2340 /// The DER or compact hex encoded signature
2341 pub signature: String,
2342}
2343
2344#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2345pub struct CheckMessageRequest {
2346 /// The message that was signed
2347 pub message: String,
2348 /// The public key that signed the message
2349 pub pubkey: String,
2350 /// The DER or compact hex encoded signature
2351 pub signature: String,
2352}
2353
2354#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2355pub struct CheckMessageResponse {
2356 pub is_valid: bool,
2357}
2358
2359#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2360#[derive(Debug, Clone, Serialize)]
2361pub struct UserSettings {
2362 pub spark_private_mode_enabled: bool,
2363
2364 /// The label of the currently active stable balance token, or `None` if deactivated.
2365 pub stable_balance_active_label: Option<String>,
2366
2367 /// The hex encoded public key designated as this wallet's master identity
2368 /// key, or `None` if none is designated.
2369 pub spark_master_identity_public_key: Option<String>,
2370}
2371
2372/// Specifies how to update the wallet's Spark master identity public key.
2373#[derive(Debug, Clone)]
2374#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2375pub enum SparkMasterIdentityPublicKey {
2376 /// Designate the holder of this public key as the wallet's master
2377 /// identity, replacing any previously designated key. Must be hex encoded
2378 /// in the 33-byte compressed form.
2379 Set { public_key: String },
2380 /// Remove the designated master identity, leaving the owner as the only
2381 /// party able to read the wallet under private mode.
2382 Unset,
2383}
2384
2385#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2386pub struct UpdateUserSettingsRequest {
2387 pub spark_private_mode_enabled: Option<bool>,
2388
2389 /// Update the active stable balance token. `None` means no change.
2390 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
2391 pub stable_balance_active_label: Option<StableBalanceActiveLabel>,
2392
2393 /// Designate or remove the wallet's master identity, a second public key
2394 /// the Spark operators accept as a reader of this wallet's balance and
2395 /// history while `spark_private_mode_enabled` is set. The master identity
2396 /// can only read: payments still require the owner's keys. `None` means no
2397 /// change.
2398 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
2399 pub spark_master_identity_public_key: Option<SparkMasterIdentityPublicKey>,
2400}
2401
2402#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2403pub struct ClaimHtlcPaymentRequest {
2404 pub preimage: String,
2405}
2406
2407#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2408pub struct ClaimHtlcPaymentResponse {
2409 pub payment: Payment,
2410}
2411
2412#[derive(Debug, Clone, Deserialize, Serialize)]
2413#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2414pub struct LnurlReceiveMetadata {
2415 pub nostr_zap_request: Option<String>,
2416 pub nostr_zap_receipt: Option<String>,
2417 pub sender_comment: Option<String>,
2418}
2419
2420/// Mode of a manually-triggered optimization run.
2421#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
2422#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2423pub enum OptimizationMode {
2424 /// Run until no further optimization is productive.
2425 #[default]
2426 Full,
2427 /// Execute a single round and return so the caller can drive progress.
2428 SingleRound,
2429}
2430
2431/// Request for [`BreezSdk::optimize_leaves`]. Defaults to
2432/// [`OptimizationMode::Full`].
2433#[derive(Debug, Clone, Default, Deserialize, Serialize)]
2434#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2435pub struct OptimizeLeavesRequest {
2436 /// Controls how much work the call performs before returning.
2437 pub mode: OptimizationMode,
2438}
2439
2440/// Response from a [`BreezSdk::optimize_leaves`] call.
2441#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
2442#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2443pub struct OptimizeLeavesResponse {
2444 /// The outcome of the optimization run.
2445 pub outcome: OptimizationOutcome,
2446}
2447
2448/// Outcome of a [`BreezSdk::optimize_leaves`] call.
2449///
2450/// `rounds_executed` on `Completed` refers to rounds run by *this call*.
2451/// The SDK holds no cross-call state — callers driving a `SingleRound`
2452/// loop maintain their own cumulative counter if they need one.
2453///
2454/// A `Completed { rounds_executed: 0 }` outcome means the wallet was
2455/// already optimal at call time (no swap was needed).
2456///
2457/// **`SingleRound` loop pattern**: terminate on anything that isn't
2458/// `InProgress`. `Completed` covers both the final swap of a productive
2459/// run and the "already optimal" no-op case (the latter as
2460/// `rounds_executed: 0`).
2461///
2462/// ```ignore
2463/// loop {
2464/// let request = OptimizeLeavesRequest { mode: OptimizationMode::SingleRound };
2465/// match sdk.optimize_leaves(request).await?.outcome {
2466/// OptimizationOutcome::InProgress => continue,
2467/// OptimizationOutcome::Completed { .. } => break,
2468/// }
2469/// }
2470/// ```
2471#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
2472#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2473pub enum OptimizationOutcome {
2474 /// All planned optimization work was executed in this call.
2475 /// Returned by `Full` runs on success, and by `SingleRound` runs
2476 /// whose swap was the final one needed (the planner produced a
2477 /// single-swap plan with a convergence guarantee).
2478 /// `rounds_executed == 0` means the wallet was already optimal —
2479 /// no work was performed.
2480 Completed { rounds_executed: u32 },
2481 /// `SingleRound` only: a round ran but the planner could not
2482 /// guarantee it was the last. The caller should invoke
2483 /// `optimize_leaves` again.
2484 InProgress,
2485}
2486
2487/// A contact entry containing a name and payment identifier.
2488#[derive(Debug, Clone, Serialize, Deserialize)]
2489#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2490pub struct Contact {
2491 pub id: String,
2492 pub name: String,
2493 /// A Lightning address (user@domain).
2494 pub payment_identifier: String,
2495 pub created_at: u64,
2496 pub updated_at: u64,
2497}
2498
2499/// Request to add a new contact.
2500#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2501pub struct AddContactRequest {
2502 pub name: String,
2503 /// A Lightning address (user@domain).
2504 pub payment_identifier: String,
2505}
2506
2507/// Request to update an existing contact.
2508#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2509pub struct UpdateContactRequest {
2510 pub id: String,
2511 pub name: String,
2512 /// A Lightning address (user@domain).
2513 pub payment_identifier: String,
2514}
2515
2516/// Request to list contacts with optional pagination.
2517#[derive(Debug, Clone, Default)]
2518#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2519pub struct ListContactsRequest {
2520 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2521 pub offset: Option<u32>,
2522 #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2523 pub limit: Option<u32>,
2524}
2525
2526/// The type of event that triggers a webhook notification.
2527#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2528#[allow(clippy::enum_variant_names)]
2529#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2530pub enum WebhookEventType {
2531 /// Triggered when a Lightning receive operation completes.
2532 LightningReceiveFinished,
2533 /// Triggered when a Lightning send operation completes.
2534 LightningSendFinished,
2535 /// Triggered when a cooperative exit completes.
2536 CoopExitFinished,
2537 /// Triggered when a static deposit completes.
2538 StaticDepositFinished,
2539 /// An event type not yet recognized by this version of the SDK.
2540 Unknown(String),
2541}
2542
2543/// A registered webhook entry.
2544#[derive(Debug, Clone, Serialize)]
2545#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2546pub struct Webhook {
2547 /// Unique identifier for this webhook.
2548 pub id: String,
2549 /// The URL that receives webhook notifications.
2550 pub url: String,
2551 /// The event types this webhook is subscribed to.
2552 pub event_types: Vec<WebhookEventType>,
2553}
2554
2555/// Request to register a new webhook.
2556#[derive(Debug, Clone)]
2557#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2558pub struct RegisterWebhookRequest {
2559 /// The URL that will receive webhook notifications.
2560 pub url: String,
2561 /// A secret used for HMAC-SHA256 signature verification of webhook payloads.
2562 pub secret: String,
2563 /// The event types to subscribe to.
2564 pub event_types: Vec<WebhookEventType>,
2565}
2566
2567/// Response from registering a webhook.
2568#[derive(Debug, Clone, Serialize)]
2569#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2570pub struct RegisterWebhookResponse {
2571 /// The unique identifier of the newly registered webhook.
2572 pub webhook_id: String,
2573}
2574
2575/// Request to unregister an existing webhook.
2576#[derive(Debug, Clone)]
2577#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2578pub struct UnregisterWebhookRequest {
2579 /// The unique identifier of the webhook to unregister.
2580 pub webhook_id: String,
2581}
2582
2583// ===========================================================================
2584// Unilateral exit
2585// ===========================================================================
2586
2587/// A funding UTXO that pays the on-chain fees of a unilateral exit.
2588#[derive(Debug, Clone, Serialize, Deserialize)]
2589#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2590pub enum CpfpInput {
2591 /// A P2WPKH (native segwit v0) UTXO controlled by `pubkey` (33-byte
2592 /// compressed, hex).
2593 P2wpkh {
2594 txid: String,
2595 vout: u32,
2596 value: u64,
2597 pubkey: String,
2598 },
2599 /// A P2TR (taproot, key-path) UTXO. `pubkey` (x-only or compressed, hex) is
2600 /// the **internal** (untweaked, BIP86 key-path) public key whose secret signs
2601 /// the input, not the tweaked on-chain output key. The SDK applies the BIP86
2602 /// taproot tweak itself to derive the funding scriptPubKey, so passing the
2603 /// already-tweaked output key here produces a scriptPubKey that does not match
2604 /// the UTXO and the built transaction is rejected at broadcast.
2605 P2tr {
2606 txid: String,
2607 vout: u32,
2608 value: u64,
2609 pubkey: String,
2610 },
2611 /// Any witness-program script, signed via a custom `CpfpSigner`. Legacy
2612 /// (non-SegWit) scripts are rejected. `signed_input_weight` (weight units)
2613 /// is an upper bound on the input's signed weight, so the fee stays exact,
2614 /// or slightly conservative if the real signature is shorter.
2615 Custom {
2616 txid: String,
2617 vout: u32,
2618 value: u64,
2619 script_pubkey_hex: String,
2620 signed_input_weight: u64,
2621 },
2622}
2623
2624/// The kind of UTXO that will fund an exit's fees.
2625#[derive(Debug, Clone, Serialize, Deserialize)]
2626#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2627pub enum CpfpFundingKind {
2628 /// Fees paid from P2WPKH (native segwit v0) UTXOs.
2629 P2wpkh,
2630 /// Fees paid from P2TR (taproot, key-path) UTXOs.
2631 P2tr,
2632 /// Fees paid from a custom witness-program script (legacy scripts are
2633 /// rejected). `script_pubkey_hex` (the funding scriptPubKey) sizes the
2634 /// fan-out output and dust; `signed_input_weight` (weight units) is an upper
2635 /// bound on the input's signed weight, so the quote stays exact or slightly
2636 /// conservative.
2637 Custom {
2638 script_pubkey_hex: String,
2639 signed_input_weight: u64,
2640 },
2641}
2642
2643/// Which leaves to exit.
2644#[derive(Debug, Clone, Serialize, Deserialize)]
2645#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2646pub enum ExitLeafSelection {
2647 /// Exit every leaf whose value exceeds its own marginal exit cost (its tree
2648 /// and refund CPFP fees plus its sweep input). This per-leaf test does not
2649 /// include the shared fan-out fee, so funding many leaves from a single UTXO
2650 /// adds `fanout_fee_sat` on top: compare `recoverable_value_sat` with
2651 /// `total_fee_sat`, or fund one UTXO per branch to avoid the fan-out. Leaves
2652 /// that fail the per-leaf test are skipped.
2653 Auto,
2654 /// Exit exactly these leaves, regardless of profitability.
2655 Specific { leaf_ids: Vec<String> },
2656}
2657
2658/// The role of a transaction in the exit path.
2659#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2660#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2661pub enum UnilateralExitTxKind {
2662 /// Splits the caller's funding into one output per branch. Present only
2663 /// when the funding couldn't be matched one-to-one to branches.
2664 FanOut,
2665 /// A tree node transaction (root, intermediate, or leaf node).
2666 Node,
2667 /// A leaf's refund transaction.
2668 Refund,
2669 /// The final transaction sweeping all refund outputs to the destination.
2670 Sweep,
2671}
2672
2673/// Whether a transaction in the exit path is already on-chain.
2674#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2675#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2676pub enum ConfirmationStatus {
2677 /// This transaction is confirmed in a block. It needs no action.
2678 Confirmed,
2679 /// This transaction is not yet confirmed. Mempool state is not consulted.
2680 Unconfirmed,
2681 /// The on-chain status could not be determined (the chain service errored).
2682 /// Broadcasting may fail if a conflicting transaction already landed.
2683 Unverified,
2684}
2685
2686/// One transaction in the unilateral exit path, with everything needed to
2687/// order and broadcast it.
2688#[derive(Debug, Clone, Serialize, Deserialize)]
2689#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2690pub struct UnilateralExitTransaction {
2691 pub kind: UnilateralExitTxKind,
2692 /// The tree node this transaction belongs to. Unset for the fan-out and the
2693 /// sweep.
2694 pub node_id: Option<String>,
2695 pub txid: String,
2696 pub tx_hex: String,
2697 /// The signed CPFP child to broadcast alongside `tx_hex` as a package.
2698 /// Unset for the fan-out and the sweep (no anchor to bump) and for a
2699 /// `Confirmed` step (its CPFP is already on-chain).
2700 pub cpfp_tx_hex: Option<String>,
2701 /// Relative CSV timelock, in blocks, that must mature on the spent input
2702 /// before this transaction can confirm. Unset when there is no timelock.
2703 pub csv_timelock_blocks: Option<u32>,
2704 /// Txids of other entries in this list that must be confirmed before this
2705 /// one can be broadcast.
2706 pub depends_on: Vec<String>,
2707 pub status: ConfirmationStatus,
2708}
2709
2710/// A leaf selected for exit, with its value.
2711#[derive(Debug, Clone, Serialize, Deserialize)]
2712#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2713pub struct UnilateralExitLeaf {
2714 pub leaf_id: String,
2715 /// The leaf's value in satoshis.
2716 pub value: u64,
2717}
2718
2719/// Request for `prepare_unilateral_exit`, the exit quote.
2720#[derive(Debug, Clone)]
2721#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2722pub struct PrepareUnilateralExitRequest {
2723 /// Target fee rate in sat/vByte, applied to every CPFP child, the fan-out,
2724 /// and the sweep.
2725 pub fee_rate_sat_per_vbyte: u64,
2726 pub funding_kind: CpfpFundingKind,
2727 /// The Bitcoin address the swept funds are sent to.
2728 pub destination: String,
2729 pub selection: ExitLeafSelection,
2730}
2731
2732/// How much to fund one branch of the exit to avoid a fan-out.
2733#[derive(Debug, Clone, Serialize, Deserialize)]
2734#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2735pub struct PerBranchFunding {
2736 /// The leaf whose branch this funds.
2737 pub leaf_id: String,
2738 /// Fund a UTXO of at least this many satoshis for this branch.
2739 pub funding_sat: u64,
2740}
2741
2742/// Response from `prepare_unilateral_exit`: which leaves would exit, the exact
2743/// fee at the requested rate, and how much to fund.
2744#[derive(Debug, Clone, Serialize, Deserialize)]
2745#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2746pub struct PrepareUnilateralExitResponse {
2747 pub leaves: Vec<UnilateralExitLeaf>,
2748 /// Total value of the selected leaves, in satoshis.
2749 pub recoverable_value_sat: u64,
2750 /// Total on-chain fee when funding with a single UTXO (fanned out across
2751 /// branches), in satoshis. Exact for the given funding kind; nodes the
2752 /// operators report on-chain are assumed already paid, so a partially-exited
2753 /// tree quotes a lower fee than a fresh one.
2754 pub total_fee_sat: u64,
2755 /// The part of `total_fee_sat` paid for the fan-out transaction. Funding one
2756 /// UTXO per branch (`per_branch_funding`) avoids it. Zero for a single
2757 /// branch (no fan-out).
2758 pub fanout_fee_sat: u64,
2759 /// Fund a single UTXO of at least this many satoshis to exit with a fan-out.
2760 pub single_utxo_funding_sat: u64,
2761 /// To skip the fan-out, fund one UTXO per branch of at least the given
2762 /// amount (one entry per selected leaf).
2763 pub per_branch_funding: Vec<PerBranchFunding>,
2764 /// The fee rate this quote was computed at, in sat/vByte.
2765 pub fee_rate_sat_per_vbyte: u64,
2766 pub destination: String,
2767}
2768
2769/// Request for `unilateral_exit`: a `prepare_unilateral_exit` quote plus the
2770/// funding UTXOs that pay its fees. The signer is passed separately (it is not a
2771/// plain data value).
2772#[derive(Debug, Clone)]
2773#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2774pub struct UnilateralExitRequest {
2775 /// The quote returned by `prepare_unilateral_exit`, naming the leaves to exit.
2776 pub prepared: PrepareUnilateralExitResponse,
2777 /// The funding UTXOs that pay the exit's on-chain fees, meeting the quote's
2778 /// `single_utxo_funding_sat` (one UTXO) or `per_branch_funding` (one per branch).
2779 pub funding_inputs: Vec<CpfpInput>,
2780}
2781
2782/// Result of `unilateral_exit`: a cost summary plus the complete, signed exit
2783/// path.
2784#[derive(Debug, Clone, Serialize)]
2785#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2786pub struct UnilateralExitResponse {
2787 /// Total value of the selected leaves, in satoshis.
2788 pub recoverable_value_sat: u64,
2789 /// The actual total on-chain fee the returned transactions pay at the
2790 /// requested rate, in satoshis. A resumed or partially-confirmed exit pays
2791 /// less because already-confirmed steps are not rebuilt.
2792 pub total_fee_sat: u64,
2793 pub leaves: Vec<UnilateralExitLeaf>,
2794 /// The full signed transaction set, in valid topological (broadcast) order
2795 /// with shared ancestors appearing once and the sweep last.
2796 pub transactions: Vec<UnilateralExitTransaction>,
2797}
2798
2799/// Result of `export_unilateral_exit_state`: a self-contained copy of the
2800/// wallet's exit state, ready to be stored outside the wallet.
2801#[derive(Debug, Clone, Serialize, Deserialize)]
2802#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2803pub struct ExportUnilateralExitStateResponse {
2804 /// The serialized exit state, to be handed back to
2805 /// `import_unilateral_exit_state` unmodified.
2806 pub exit_state: String,
2807}
2808
2809/// Request for `import_unilateral_exit_state`.
2810#[derive(Debug, Clone, Serialize, Deserialize)]
2811#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2812pub struct ImportUnilateralExitStateRequest {
2813 /// An exit state as returned by `export_unilateral_exit_state`.
2814 pub exit_state: String,
2815}
2816
2817/// Result of `import_unilateral_exit_state`.
2818#[derive(Debug, Clone, Serialize, Deserialize)]
2819#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2820pub struct ImportUnilateralExitStateResponse {
2821 /// Leaves merged into the wallet's exit state, whether or not their exit
2822 /// data was taken with them.
2823 pub imported_leaves: u32,
2824 /// Leaves left out because the exit state does not record this wallet as
2825 /// their owner.
2826 pub skipped_foreign_leaves: u32,
2827 /// Leaves left out because their exit data disagrees with what the wallet
2828 /// already holds, so none of it could be trusted. The wallet is left without
2829 /// these leaves.
2830 pub skipped_conflicting_leaves: u32,
2831 /// Leaves the wallet holds whose imported exit data was left out: it is
2832 /// incomplete, the wallet's own copy can already back an exit, or the leaf
2833 /// was named more than once. The leaf itself is in the wallet either way.
2834 pub skipped_chains: u32,
2835}