diff --git a/README.md b/README.md index 9973476..528cc77 100644 --- a/README.md +++ b/README.md @@ -138,20 +138,49 @@ The wallet's behavior is controlled by `Tunables`: ## Supported Backends ### Trusted Wallet Backends -- **Spark** - Custodial Lightning wallet (default) -- **Cashu** - Ecash-based wallet + +#### Spark (default) + +Spark uses the [Breez Spark SDK](https://breez.technology) for custodial Lightning payments with instant settlement and low fees. This is the default backend and is enabled via the `spark` feature flag. + +```rust,no_run +use orange_sdk::trusted_wallet::spark::SparkWalletConfig; +use orange_sdk::ExtraConfig; + +let extra = ExtraConfig::Spark(SparkWalletConfig::default()); +``` + +#### Cashu + +Cashu uses the [Cashu Development Kit (CDK)](https://docs.rs/cdk) for ecash-based custody via a Cashu mint. Enable it with the `cashu` feature flag: + +```toml +[dependencies] +orange-sdk = { version = "0.1", features = ["cashu"] } +``` + +```rust,ignore +use orange_sdk::trusted_wallet::cashu::CashuConfig; +use orange_sdk::{CurrencyUnit, ExtraConfig}; + +let extra = ExtraConfig::Cashu(CashuConfig { + mint_url: "https://mint.example.com".to_string(), + unit: CurrencyUnit::Sat, + npubcash_url: Some("https://npub.cash".to_string()), // optional: enables Lightning address +}); +``` ### Chain Sources -- Electrum servers -- Esplora servers (with optional Basic auth) -- Bitcoin Core RPC +- **Electrum** servers (use `ssl://` prefix for TLS) +- **Esplora** HTTP API servers (with optional Basic auth) +- **Bitcoin Core RPC** (direct JSON-RPC connection) ## Documentation For detailed API documentation, run: ```bash -cargo doc --open +cargo doc --all-features --open ``` ## Contributing diff --git a/graduated-rebalancer/src/lib.rs b/graduated-rebalancer/src/lib.rs index fa65da8..27d35e3 100644 --- a/graduated-rebalancer/src/lib.rs +++ b/graduated-rebalancer/src/lib.rs @@ -1,11 +1,34 @@ #![deny(missing_docs)] #![allow(clippy::type_complexity)] -//! A library for managing graduated rebalancing between trusted and lightning wallets. +//! Graduated rebalancing between trusted and Lightning wallets. //! -//! This crate provides a `GraduatedRebalancer` that automatically manages the balance -//! between trusted wallets (for small amounts) and lightning wallets (for larger amounts) -//! based on configurable thresholds. +//! This crate provides [`GraduatedRebalancer`], a generic engine that automatically moves +//! funds from a trusted wallet backend into self-custodial Lightning channels based on +//! configurable thresholds. +//! +//! # How it works +//! +//! The rebalancer supports two transfer paths: +//! +//! 1. **Trusted → Lightning:** When the trusted wallet balance exceeds the configured limit, +//! the rebalancer pays a Lightning invoice from the trusted wallet to the self-custodial +//! Lightning node, effectively moving funds into self-custody. +//! +//! 2. **On-chain → Lightning:** When on-chain funds are available, the rebalancer opens a +//! new Lightning channel or splices funds into an existing channel with the LSP. +//! +//! Both paths are triggered via the [`RebalanceTrigger`] trait, which determines *when* and +//! *how much* to rebalance. Events are reported via the [`EventHandler`] trait. +//! +//! # Usage +//! +//! This crate is designed to be generic over wallet implementations. Provide types that +//! implement [`TrustedWallet`] and [`LightningWallet`], along with a [`RebalanceTrigger`] +//! and an [`EventHandler`], then construct a [`GraduatedRebalancer`]. +//! +//! The [`orange-sdk`](https://docs.rs/orange-sdk) crate provides a concrete integration +//! of this rebalancer with its wallet infrastructure. use bitcoin_payment_instructions::amount::Amount; use bitcoin_payment_instructions::PaymentMethod; @@ -206,7 +229,16 @@ impl EventHandler for IgnoringEventHandler { } } -/// The main graduated rebalancer that manages balance between trusted and lightning wallets +/// The core rebalancing engine. +/// +/// Manages the automatic transfer of funds from a trusted wallet into self-custodial +/// Lightning channels. It is generic over the wallet implementations, trigger logic, +/// event handling, and logger. +/// +/// All rebalance operations are serialized through an internal mutex to prevent +/// concurrent balance modifications. +/// +/// See the [crate-level documentation](crate) for an overview of how rebalancing works. pub struct GraduatedRebalancer< T: TrustedWallet, L: LightningWallet, diff --git a/orange-sdk/src/event.rs b/orange-sdk/src/event.rs index 87d179d..4e2d044 100644 --- a/orange-sdk/src/event.rs +++ b/orange-sdk/src/event.rs @@ -23,7 +23,29 @@ pub(crate) const EVENT_QUEUE_PERSISTENCE_PRIMARY_NAMESPACE: &str = ""; pub(crate) const EVENT_QUEUE_PERSISTENCE_SECONDARY_NAMESPACE: &str = ""; pub(crate) const EVENT_QUEUE_PERSISTENCE_KEY: &str = "orange_events"; -/// An event emitted by [`Wallet`], which should be handled by the user. +/// An event emitted by [`Wallet`] that should be handled by the user. +/// +/// Events are retrieved via [`Wallet::next_event`](crate::Wallet::next_event), +/// [`Wallet::next_event_async`](crate::Wallet::next_event_async), or +/// [`Wallet::wait_next_event`](crate::Wallet::wait_next_event). After processing an event, +/// you **must** call [`Wallet::event_handled`](crate::Wallet::event_handled) to advance the queue. +/// +/// Events are persisted, so they will survive process restarts and will be re-delivered +/// until acknowledged. +/// +/// # Event types +/// +/// | Event | Meaning | +/// |-------|---------| +/// | [`PaymentSuccessful`](Self::PaymentSuccessful) | An outgoing payment completed | +/// | [`PaymentFailed`](Self::PaymentFailed) | An outgoing payment failed | +/// | [`PaymentReceived`](Self::PaymentReceived) | An incoming Lightning payment arrived | +/// | [`OnchainPaymentReceived`](Self::OnchainPaymentReceived) | An incoming on-chain payment arrived | +/// | [`ChannelOpened`](Self::ChannelOpened) | A Lightning channel is ready to use | +/// | [`ChannelClosed`](Self::ChannelClosed) | A Lightning channel was closed (rebalancing auto-disabled) | +/// | [`RebalanceInitiated`](Self::RebalanceInitiated) | A trusted-to-Lightning rebalance started | +/// | [`RebalanceSuccessful`](Self::RebalanceSuccessful) | A trusted-to-Lightning rebalance completed | +/// | [`SplicePending`](Self::SplicePending) | An on-chain splice into a channel is pending confirmation | /// /// [`Wallet`]: [`crate::Wallet`] #[derive(Debug, Clone, PartialEq, Eq)] @@ -201,9 +223,16 @@ impl_writeable_tlv_based_enum!(Event, }, ); -/// A queue for events emitted by the [`Wallet`]. +/// A persistent, ordered queue of [`Event`]s emitted by the [`Wallet`]. +/// +/// The queue is backed by the wallet's [`KVStore`] so events survive restarts. Events are +/// delivered in FIFO order and remain at the head of the queue until acknowledged via +/// [`Wallet::event_handled`](crate::Wallet::event_handled). +/// +/// Users typically interact with this through the [`Wallet`] methods rather than directly. /// /// [`Wallet`]: [`crate::Wallet`] +/// [`KVStore`]: ldk_node::lightning::util::persist::KVStore pub struct EventQueue { queue: Arc>>, waker: Arc>>, diff --git a/orange-sdk/src/lib.rs b/orange-sdk/src/lib.rs index 3388a60..0d0ffcd 100644 --- a/orange-sdk/src/lib.rs +++ b/orange-sdk/src/lib.rs @@ -75,21 +75,43 @@ type Rebalancer = GraduatedRebalancer< Logger, >; -/// Represents the balances of the wallet, including available and pending balances. +/// The wallet's balance breakdown across its different storage layers. +/// +/// Funds in an orange-sdk [`Wallet`] live in up to three places: +/// +/// | Layer | Field | Spendable? | +/// |-------|-------|------------| +/// | Trusted backend (Spark / Cashu) | [`trusted`](Self::trusted) | Yes | +/// | Lightning channel | [`lightning`](Self::lightning) | Yes | +/// | On-chain (pending splice / channel open) | [`pending_balance`](Self::pending_balance) | No | +/// +/// Use [`available_balance`](Self::available_balance) to get the total amount the wallet can +/// spend right now (trusted + lightning). #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Balances { - /// The balance in trusted wallet + /// Balance held in the trusted wallet backend (e.g. Spark or Cashu). + /// + /// These funds are available for instant, low-fee payments but are custodied + /// by a third party. pub trusted: Amount, - /// The balance in lightning wallet available for spending. + /// Balance available in self-custodial Lightning channels. + /// + /// These funds are fully self-custodial and can be spent over Lightning + /// without any third-party trust. pub lightning: Amount, - /// The balance that is pending and not yet spendable. - /// This includes all on-chain balances. The on-chain balance will become - /// available after it has been spliced into a lightning channel. + /// Balance that is not yet spendable. + /// + /// This includes all on-chain balances (e.g. funds waiting for a channel open + /// or splice-in to confirm). Once the on-chain transaction confirms and the + /// channel is ready, these funds move into [`lightning`](Self::lightning). pub pending_balance: Amount, } impl Balances { - /// Returns the total available balance, which is the sum of the lightning and trusted balances. + /// Returns the total spendable balance (trusted + lightning). + /// + /// This excludes [`pending_balance`](Self::pending_balance) since those funds are not + /// yet available for spending. pub fn available_balance(&self) -> Amount { self.lightning.saturating_add(self.trusted) } @@ -137,17 +159,27 @@ pub struct Wallet { inner: Arc, } -/// Represents the seed used for wallet generation. +/// The secret key material used to derive all wallet keys. +/// +/// Two representations are supported: +/// +/// - [`Mnemonic`](Self::Mnemonic) – a standard BIP 39 mnemonic phrase (12 or 24 words). +/// This is the recommended form for end-user wallets because it can be backed up on paper. +/// - [`Seed64`](Self::Seed64) – a raw 64-byte seed, useful for programmatic key derivation +/// or when the seed is already available from another source. +/// +/// The same seed will always produce the same wallet addresses and keys, which is what +/// enables recovery (see [`Wallet::new`] for recovery details). #[derive(Debug, Clone)] pub enum Seed { - /// A BIP 39 mnemonic seed. + /// A BIP 39 mnemonic seed phrase. Mnemonic { - /// The mnemonic phrase. + /// The mnemonic phrase (typically 12 or 24 words). mnemonic: Mnemonic, - /// The passphrase for the mnemonic. + /// Optional BIP 39 passphrase (sometimes called the "25th word"). passphrase: Option, }, - /// A 64-byte seed for the wallet. + /// A raw 64-byte seed for the wallet. Seed64([u8; 64]), } @@ -172,84 +204,137 @@ pub struct VssConfig { pub headers: VssAuth, } -/// Configuration for wallet storage, either local SQLite or VSS. +/// Configuration for wallet persistence. +/// +/// Controls where the wallet stores channel state, transaction metadata, and event history. +/// +/// Currently only local SQLite is supported. A VSS (Versioned Storage Service) backend is +/// planned, which will enable cross-device state synchronization and Lightning channel recovery +/// from seed. #[derive(Debug, Clone)] pub enum StorageConfig { - /// Local SQLite database configuration. + /// Store all data in a local SQLite database at the given directory path. + /// + /// The directory will be created if it does not exist. LocalSQLite(String), // todo VSS(VssConfig), } -/// Configuration for the blockchain data source. +/// The blockchain data source used to monitor on-chain transactions and fee rates. +/// +/// The wallet needs a connection to the Bitcoin network to track on-chain balances, +/// confirm channel opens/closes, and estimate fees. Three backend types are supported: +/// +/// - [`Electrum`](Self::Electrum) – connects to an Electrum server (supports SSL via `ssl://` prefix). +/// - [`Esplora`](Self::Esplora) – connects to an Esplora HTTP API (e.g. `https://blockstream.info/api`). +/// - [`BitcoindRPC`](Self::BitcoindRPC) – connects directly to a Bitcoin Core node via JSON-RPC. #[derive(Debug, Clone)] pub enum ChainSource { - /// Electrum server configuration. + /// Connect to an Electrum server. + /// + /// Use the `ssl://` prefix for TLS connections (e.g. `ssl://electrum.blockstream.info:60002`). Electrum(String), - /// Esplora server configuration. + /// Connect to an Esplora HTTP API, with optional Basic authentication. Esplora { - /// Esplora url + /// The base URL of the Esplora server (e.g. `https://blockstream.info/api`). url: String, - /// Optional for Basic authentication for the Esplora server. + /// Optional username for HTTP Basic authentication. username: Option, - /// Optional for Basic authentication for the Esplora server. + /// Optional password for HTTP Basic authentication. password: Option, }, - /// Bitcoind RPC configuration. + /// Connect directly to a Bitcoin Core node via JSON-RPC. BitcoindRPC { - /// The host of the Bitcoind rpc server (e.g. 127.0.0.1). + /// The host of the Bitcoin Core RPC server (e.g. `127.0.0.1`). host: String, - /// The port of the Bitcoind rpc server (e.g. 8332). + /// The port of the Bitcoin Core RPC server (e.g. `8332` for mainnet). port: u16, - /// The username for the Bitcoind rpc server. + /// The RPC username (configured in `bitcoin.conf`). user: String, - /// The password for the Bitcoind rpc server. + /// The RPC password (configured in `bitcoin.conf`). password: String, }, } -/// Configuration for initializing the wallet. +/// Everything needed to initialize a [`Wallet`]. +/// +/// This struct bundles together all the configuration required to create a wallet instance: +/// storage, networking, keys, thresholds, and the trusted wallet backend. +/// +/// See the [crate-level documentation](crate) for a full configuration example. #[derive(Clone)] pub struct WalletConfig { - /// Configuration for wallet storage. + /// Where the wallet persists its state (channel data, transaction metadata, events). pub storage_config: StorageConfig, - /// The type of logger to use. + /// How the wallet emits log output. pub logger_type: LoggerType, - /// Configuration for the blockchain data source. + /// The blockchain data source for on-chain monitoring and fee estimation. pub chain_source: ChainSource, - /// Lightning Service Provider (LSP) configuration. - /// The address to connect to the LSP, the LSP node id, and an optional auth token. + /// Lightning Service Provider (LSP) connection details. + /// + /// The tuple contains: + /// 1. The LSP's network address (e.g. `127.0.0.1:9735`) + /// 2. The LSP's node public key + /// 3. An optional authentication token + /// + /// The LSP is used to open JIT (Just-In-Time) channels for receiving Lightning payments. pub lsp: (SocketAddress, PublicKey, Option), - /// URL to download a scorer from. This is for the lightning node to get its route - /// scorer from a remote server instead of having to probe and find optimal routes - /// locally. + /// Optional URL to download a pre-built route scorer. + /// + /// When set, the Lightning node fetches its route scorer from this remote server instead of + /// probing and discovering optimal routes locally. This speeds up initial routing decisions. pub scorer_url: Option, - /// URL to Rapid Gossip Sync server to get gossip data from. + /// Optional URL to a [Rapid Gossip Sync](https://docs.rs/lightning-rapid-gossip-sync) server. + /// + /// When set, the Lightning node downloads compressed gossip data from this server instead + /// of learning the network graph through peer gossip, significantly reducing sync time. pub rgs_url: Option, - /// The Bitcoin network the wallet operates on. + /// The Bitcoin network to operate on (e.g. `Network::Bitcoin`, `Network::Testnet`). pub network: Network, - /// The seed used for wallet generation. + /// The secret key material for this wallet. See [`Seed`] for options. pub seed: Seed, - /// Configuration parameters for when the wallet decides to use the lightning or trusted wallet. + /// Thresholds that control when funds move between the trusted and Lightning wallets. pub tunables: Tunables, - /// Extra configuration specific to the trusted wallet implementation. + /// Backend-specific configuration for the trusted wallet (Spark, Cashu, etc.). pub extra_config: ExtraConfig, } -/// Configuration parameters for when the wallet decides to use the lightning or trusted wallet. +/// Thresholds that control the wallet's graduated custody behavior. +/// +/// These parameters govern how the wallet distributes funds between the trusted backend +/// and self-custodial Lightning channels, and how it generates payment URIs. +/// +/// The default values are a reasonable starting point for most wallets: +/// +/// | Parameter | Default | Purpose | +/// |-----------|---------|---------| +/// | `trusted_balance_limit` | 100,000 sats | Trigger rebalance to Lightning above this | +/// | `rebalance_min` | 5,000 sats | Don't bother rebalancing amounts smaller than this | +/// | `onchain_receive_threshold` | 10,000 sats | Include on-chain address in receive URIs above this | +/// | `enable_amountless_receive_on_chain` | `true` | Include on-chain address for open-amount receives | #[derive(Debug, Clone, Copy)] pub struct Tunables { - /// The maximum balance that can be held in the trusted wallet. + /// The maximum balance allowed in the trusted wallet before triggering automatic rebalancing. + /// + /// When the trusted balance exceeds this limit, excess funds are automatically moved into + /// a self-custodial Lightning channel. Set this based on how much you're comfortable + /// holding in the trusted backend. pub trusted_balance_limit: Amount, - /// Trusted balances below this threshold will not be transferred to non-trusted balance - /// even if we have capacity to do so without paying for a new channel. + /// The minimum amount worth rebalancing from trusted to Lightning. /// - /// This avoids unnecessary transfers and fees. + /// Amounts below this threshold won't be transferred even if there's available Lightning + /// capacity, avoiding unnecessary small transfers and their associated fees. pub rebalance_min: Amount, - /// Payment instructions generated using [`Wallet::get_single_use_receive_uri`] for an amount - /// below this threshold will not include an on-chain address. + /// The minimum receive amount for which an on-chain address is included in payment URIs. + /// + /// When generating a receive URI via [`Wallet::get_single_use_receive_uri`], amounts + /// below this threshold will only include a Lightning invoice (no on-chain fallback). + /// This avoids on-chain dust for small payments. pub onchain_receive_threshold: Amount, - /// Payment instructions generated using [`Wallet::get_single_use_receive_uri`] with no amount - /// will only include an on-chain address if this is set. + /// Whether to include an on-chain address in open-amount (no specific amount) receive URIs. + /// + /// When `true`, [`Wallet::get_single_use_receive_uri`] called with `amount: None` will + /// include an on-chain address alongside the Lightning invoice. pub enable_amountless_receive_on_chain: bool, } @@ -264,7 +349,8 @@ impl Default for Tunables { } } -/// Represents errors that can occur when building a [`PaymentInfo`] from [`PaymentInstructions`]. +/// Errors returned by [`PaymentInfo::build`] when the provided amount is incompatible +/// with the [`PaymentInstructions`]. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum PaymentInfoBuildError { /// The amount given does not match either of the fixed amounts specified in the @@ -290,7 +376,10 @@ pub enum PaymentInfoBuildError { }, } -/// A payable version of [`PaymentInstructions`] (i.e. with a set amount). +/// A validated, ready-to-pay combination of [`PaymentInstructions`] and an [`Amount`]. +/// +/// Created via [`PaymentInfo::build`], which validates that the amount is compatible +/// with the payment instructions. Pass this to [`Wallet::pay`] to initiate the payment. #[derive(Debug, Clone, PartialEq, Eq)] pub struct PaymentInfo { /// The payment instructions (e.g., BOLT 11 invoice, on-chain address). @@ -300,13 +389,16 @@ pub struct PaymentInfo { } impl PaymentInfo { - /// Prepares us to pay a [`PaymentInstructions`] by setting the amount. + /// Creates a [`PaymentInfo`] by pairing [`PaymentInstructions`] with an amount. + /// + /// The amount is validated against the payment instructions: /// - /// If [`PaymentInstructions`] is a [`PaymentInstructions::ConfigurableAmount`], the amount must be - /// within the specified range (if any). + /// - **[`ConfigurableAmount`](PaymentInstructions::ConfigurableAmount):** the amount is + /// required and must fall within the optional min/max range. + /// - **[`FixedAmount`](PaymentInstructions::FixedAmount):** the amount is optional (it + /// defaults to the fixed amount) but if provided must match. /// - /// If [`PaymentInstructions`] is a [`PaymentInstructions::FixedAmount`], the amount must match the - /// fixed on-chain or lightning amount specified. + /// Returns [`PaymentInfoBuildError`] if the amount is missing, out of range, or mismatched. pub fn build( instructions: PaymentInstructions, amount: Option, ) -> Result { @@ -397,16 +489,19 @@ impl PaymentInfo { } } -/// Represents possible failures during wallet initialization. +/// Errors that can occur during [`Wallet::new`]. +/// +/// Initialization can fail due to I/O issues (e.g. storage), Lightning node setup errors, +/// or problems connecting to the trusted wallet backend. #[derive(Debug)] pub enum InitFailure { - /// I/O error during initialization. + /// An I/O error occurred (e.g. failed to create storage directory). IoError(io::Error), - /// Failure to build the LDK node. + /// Failed to build the underlying LDK node (invalid configuration). LdkNodeBuildFailure(BuildError), - /// Failure to start the LDK node. + /// Failed to start the underlying LDK node (e.g. port already in use). LdkNodeStartFailure(NodeError), - /// Failure in the trusted wallet implementation. + /// The trusted wallet backend failed to initialize. TrustedFailure(TrustedError), } @@ -434,12 +529,12 @@ impl From for InitFailure { } } -/// Represents possible errors during wallet operations. +/// Errors that can occur during wallet operations (payments, balance queries, etc.). #[derive(Debug)] pub enum WalletError { - /// Failure in the LDK node. + /// The self-custodial Lightning node encountered an error. LdkNodeFailure(NodeError), - /// Failure in the trusted wallet implementation. + /// The trusted wallet backend encountered an error. TrustedFailure(TrustedError), } @@ -455,18 +550,37 @@ impl From for WalletError { } } -/// Represents a single-use Bitcoin URI for receiving payments. +/// A single-use [BIP 21](https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki) Bitcoin +/// URI for receiving a payment. +/// +/// Generated by [`Wallet::get_single_use_receive_uri`]. The URI may contain both an on-chain +/// address and a BOLT 11 Lightning invoice (a "unified" URI), allowing the payer to choose the +/// best payment method. Whether an on-chain address is included depends on the wallet's +/// [`Tunables`] and the requested amount. +/// +/// The [`Display`](std::fmt::Display) implementation formats this as a BIP 21 URI string +/// suitable for encoding in a QR code. #[derive(Debug, Clone, PartialEq, Eq)] pub struct SingleUseReceiveUri { - /// The optional on-chain Bitcoin address. Will be present based on the - /// wallet's configured tunables. + /// The on-chain Bitcoin address, if included. + /// + /// Present when the requested amount exceeds [`Tunables::onchain_receive_threshold`], + /// or when [`Tunables::enable_amountless_receive_on_chain`] is `true` and no amount + /// was specified. pub address: Option, - /// The BOLT 11 Lightning invoice. + /// The BOLT 11 Lightning invoice for this payment. pub invoice: Bolt11Invoice, - /// The optional amount for the payment. + /// The requested amount, if one was specified. pub amount: Option, - /// Whether the URI was generated from a trusted wallet or from - /// the self-custodial LN wallet. + /// Whether the invoice was generated by the trusted wallet backend or the + /// self-custodial LDK node. + /// + /// When `amount` is `Some`, this reflects the routing decision: small amounts + /// use the trusted backend (`true`), larger amounts use the LDK node (`false`). + /// + /// When `amount` is `None` (amountless receive), this is always `false` even + /// though the invoice is generated by the trusted backend, since the wallet + /// cannot predict which layer will ultimately receive the payment. pub from_trusted: bool, } @@ -661,7 +775,11 @@ impl Wallet { Ok(Wallet { inner }) } - /// Sets whether the wallet should automatically rebalance from trusted/onchain to lightning. + /// Enables or disables automatic rebalancing from trusted/on-chain to Lightning. + /// + /// Rebalancing is enabled by default. It is automatically disabled when a channel closes + /// (to avoid an open-close loop). Call this with `true` to re-enable it after handling + /// a [`Event::ChannelClosed`] event. pub async fn set_rebalance_enabled(&self, value: bool) { store::set_rebalance_enabled(self.inner.store.as_ref(), value).await } @@ -671,7 +789,9 @@ impl Wallet { store::get_rebalance_enabled(self.inner.store.as_ref()).await } - /// Returns the lightning wallet's node id. + /// Returns the public key of the underlying Lightning node. + /// + /// This is the node's identity on the Lightning Network and can be shared with peers. pub fn node_id(&self) -> PublicKey { self.inner.ln_wallet.inner.ldk_node.node_id() } @@ -681,12 +801,19 @@ impl Wallet { self.inner.ln_wallet.is_connected_to_lsp() } - /// List our current channels + /// Lists all open Lightning channels and their details (capacity, balance, state, etc.). pub fn channels(&self) -> Vec { self.inner.ln_wallet.inner.ldk_node.list_channels() } - /// Lists the transactions which have been made. + /// Lists completed transactions and in-flight outbound payments, sorted by time. + /// + /// Returns a unified list covering both trusted and self-custodial payments. + /// Internal rebalance transfers are merged into single logical transactions + /// with combined fees. + /// + /// **Note:** Pending inbound invoices (issued but unpaid) and pending rebalances + /// are excluded from the results. pub async fn list_transactions(&self) -> Result, WalletError> { let (trusted_payments, splice_outs) = tokio::join!( self.inner.trusted.list_payments(), @@ -956,7 +1083,9 @@ impl Wallet { Ok(res) } - /// Gets our current total balance + /// Returns the wallet's current balance across all layers (trusted, Lightning, and pending). + /// + /// See [`Balances`] for details on each field. pub async fn get_balance(&self) -> Result { let trusted_balance = self.inner.trusted.get_balance().await?; let ln_balance = self.inner.ln_wallet.get_balance(); @@ -1074,20 +1203,29 @@ impl Wallet { // // } - /// Estimates the fees required to pay a [`PaymentInstructions`] + /// Estimates the fees required to pay a [`PaymentInstructions`]. + /// + /// **Note:** Fee estimation is not yet implemented and currently always returns zero. pub async fn estimate_fee(&self, _payment_info: &PaymentInstructions) -> Amount { // todo implement fee estimation Amount::ZERO } - /// Initiates a payment using the provided [`PaymentInfo`]. This will pay from the trusted - /// wallet if possible, otherwise it will pay from the lightning wallet. + /// Sends a payment using the provided [`PaymentInfo`]. + /// + /// The wallet automatically selects the best funding source using this priority: /// - /// If applicable, this will also initiate a rebalance from the trusted wallet to the - /// lightning wallet based on the resulting balance and configured tunables. + /// 1. **Trusted wallet over Lightning** (BOLT 11/12) – lowest fees for small payments + /// 2. **Self-custodial Lightning** (BOLT 11/12) – if trusted balance is insufficient + /// 3. **Trusted wallet on-chain** – for on-chain payment methods + /// 4. **Self-custodial on-chain** (splice-out) – last resort /// - /// Returns once the payment is pending, however, this does not mean that the - /// payment has been completed. The payment may still fail. + /// After a successful payment, automatic rebalancing may be triggered if the + /// resulting trusted balance exceeds [`Tunables::trusted_balance_limit`]. + /// + /// Returns a [`PaymentId`] once the payment has been **initiated**. The payment + /// may still be in-flight; listen for [`Event::PaymentSuccessful`] or + /// [`Event::PaymentFailed`] to confirm the outcome. pub async fn pay(&self, instructions: &PaymentInfo) -> Result { let trusted_balance = self.inner.trusted.get_balance().await?; let ln_balance = self.inner.ln_wallet.get_balance(); @@ -1295,9 +1433,18 @@ impl Wallet { )) } - /// Initiates closing all channels in the lightning wallet. The channel will not be closed - /// until a [`Event::ChannelClosed`] event is emitted. - /// This will disable rebalancing before closing channels, so that we don't try to reopen them. + /// Initiates closing all open Lightning channels. + /// + /// Usable channels are closed cooperatively; non-usable channels (e.g. peer offline) + /// are force-closed. Force closes have higher on-chain fees and a time-locked delay + /// before funds become spendable. + /// + /// This automatically disables rebalancing (see [`set_rebalance_enabled`](Self::set_rebalance_enabled)) + /// to prevent the wallet from immediately reopening channels. + /// + /// The close is asynchronous — channels are not fully closed until you receive + /// [`Event::ChannelClosed`] events. On-chain funds from the closed channels will + /// appear in [`Balances::pending_balance`] until confirmed. pub async fn close_channels(&self) -> Result<(), WalletError> { // we are explicitly disabling rebalancing here, so that we don't try to // reopen channels after closing them. @@ -1316,7 +1463,7 @@ impl Wallet { // Ok(()) // } - /// Returns the wallet's configured tunables. + /// Returns the [`Tunables`] that were used to configure this wallet. pub fn get_tunables(&self) -> Tunables { self.inner.tunables } @@ -1381,18 +1528,26 @@ impl Wallet { res } - /// Gets the lightning address for this wallet, if one is set. + /// Returns the wallet's registered [Lightning Address](https://lightningaddress.com), + /// if one has been set via [`register_lightning_address`](Self::register_lightning_address). pub async fn get_lightning_address(&self) -> Result, WalletError> { Ok(self.inner.trusted.get_lightning_address().await?) } - /// Attempts to register the lightning address for this wallet. + /// Registers a [Lightning Address](https://lightningaddress.com) (e.g. `name@domain.com`) + /// for this wallet. + /// + /// The `name` parameter is the local part (before the `@`). The domain is determined + /// by the trusted wallet backend configuration. pub async fn register_lightning_address(&self, name: String) -> Result<(), WalletError> { Ok(self.inner.trusted.register_lightning_address(name).await?) } - /// Stops the wallet, which will stop the underlying LDK node and any background tasks. - /// This will ensure that any critical tasks have completed before stopping. + /// Gracefully shuts down the wallet. + /// + /// This waits for any in-progress rebalances to complete, stops the trusted wallet + /// backend, shuts down the Lightning node, and cancels background tasks. Call this + /// before dropping the wallet to ensure data is persisted and resources are released. pub async fn stop(&self) { // wait for the balance mutex to ensure no other tasks are running log_info!(self.inner.logger, "Stopping..."); diff --git a/orange-sdk/src/store.rs b/orange-sdk/src/store.rs index f6eb15a..5b89515 100644 --- a/orange-sdk/src/store.rs +++ b/orange-sdk/src/store.rs @@ -1,15 +1,9 @@ -//! A library implementing the full backend for a modern, highly usable, Bitcoin wallet focusing on -//! maximizing security and self-custody without trading off user experience. +//! Transaction metadata storage and types. //! -//! This crate should do everything you need to build a great Bitcoin wallet, except the UI. -//! -//! In order to maximize the user experience, small balances are held in a trusted service (XXX -//! which one), avoiding expensive setup fees, while larger balances are moved into on-chain -//! lightning channels, ensuring trust is minimized in the trusted service. -//! -//! Despite funds being stored in multiple places, the full balance can be treated as a single -//! wallet - payments can draw on both balances simultaneously and deposits are automatically -//! shifted to minimize fees and ensure maximal security. +//! This module defines the public types used to represent transactions ([`Transaction`], +//! [`PaymentId`], [`TxStatus`], [`PaymentType`]) and the internal storage layer +//! ([`TxMetadataStore`]) that tracks payment metadata across both trusted and self-custodial +//! wallets. use bitcoin_payment_instructions::amount::Amount; @@ -34,14 +28,14 @@ const STORE_PRIMARY_KEY: &str = "orange_sdk"; const STORE_SECONDARY_KEY: &str = "payment_store"; const SPLICE_OUT_SECONDARY_KEY: &str = "splice_out"; -/// The status of a transaction. This is used to track the state of a transaction +/// The lifecycle state of a [`Transaction`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TxStatus { - /// A pending transaction has not yet been paid. + /// The payment has been initiated but not yet settled. Pending, - /// A completed transaction has been paid. + /// The payment settled successfully. Completed, - /// A transaction that has failed. + /// The payment failed (e.g. no route, insufficient funds, timeout). Failed, } @@ -67,28 +61,31 @@ impl Readable for TxStatus { } } -/// A transaction is a record of a payment made or received. It contains information about the -/// transaction, such as the amount, fee, and status. It is used to track the state of a payment -/// and to provide information about the payment to the user. +/// A unified record of a payment made or received, returned by [`Wallet::list_transactions`](crate::Wallet::list_transactions). +/// +/// Transactions cover both trusted and self-custodial payments. Internal rebalance +/// transfers are merged into single entries with combined fees. #[derive(Debug, Clone)] pub struct Transaction { - /// The unique identifier for the payment. + /// Unique identifier for this payment. + /// + /// Use the variant ([`PaymentId::Trusted`] vs [`PaymentId::SelfCustodial`]) to determine + /// which wallet layer handled the payment. pub id: PaymentId, - /// The transaction status, either (Pending, Completed, or Failed) + /// Current lifecycle state of this transaction. pub status: TxStatus, - /// Indicates whether the payment is outbound (`true`) or inbound (`false`). + /// `true` for outbound (sent) payments, `false` for inbound (received). pub outbound: bool, - /// The amount of the payment - /// - /// None if the payment is not yet completed + /// The payment amount, or `None` if not yet known (e.g. pending inbound). pub amount: Option, - /// The fee paid for the payment + /// The fee paid for this transaction, or `None` if not yet known. /// - /// None if the payment is not yet completed + /// For internal rebalance transfers, this is the combined fee across both + /// the trusted and Lightning legs of the transfer. pub fee: Option, - /// Represents the type of payment, including its method and associated metadata. + /// The payment method and associated metadata (Lightning BOLT 11/12, on-chain, etc.). pub payment_type: PaymentType, - /// The time the transaction was created + /// When this transaction was created, as a duration since the Unix epoch. pub time_since_epoch: Duration, } @@ -128,14 +125,16 @@ impl From for StoreTransaction { } } -/// A PaymentId is a unique identifier for a payment. It can be either a Lightning payment or a -/// Trusted payment. It is used to track the state of a payment and to provide information about -/// the payment to the user. +/// A unique identifier for a payment, tagged by which wallet layer handled it. +/// +/// The string representation uses a `SC-` prefix for self-custodial payments and a `TR-` +/// prefix for trusted payments, followed by the hex-encoded 32-byte ID. This format +/// round-trips via [`Display`](std::fmt::Display) and [`FromStr`](std::str::FromStr). #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub enum PaymentId { - /// A self-custodial payment identifier. + /// A payment handled by the self-custodial Lightning node. SelfCustodial([u8; 32]), - /// A trusted payment identifier. + /// A payment handled by the trusted wallet backend (Spark, Cashu, etc.). Trusted([u8; 32]), } @@ -173,7 +172,10 @@ impl_writeable_tlv_based_enum!(PaymentId, {1, Trusted} => (), ); -/// Represents the type of payment, including its method and associated metadata. +/// The payment method and associated metadata for a [`Transaction`]. +/// +/// For outgoing Lightning payments, the `payment_preimage` field serves as cryptographic +/// proof of payment and is populated once the payment reaches [`TxStatus::Completed`]. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum PaymentType { /// An outgoing Lightning payment paying a BOLT 12 offer. diff --git a/orange-sdk/src/trusted_wallet/cashu/mod.rs b/orange-sdk/src/trusted_wallet/cashu/mod.rs index 404791e..a9454f2 100644 --- a/orange-sdk/src/trusted_wallet/cashu/mod.rs +++ b/orange-sdk/src/trusted_wallet/cashu/mod.rs @@ -1,4 +1,25 @@ -//! An implementation of `TrustedWalletInterface` using the Cashu (CDK) SDK. +//! Cashu ecash trusted wallet backend. +//! +//! This module implements [`TrustedWalletInterface`] using the +//! [Cashu Development Kit (CDK)](https://docs.rs/cdk). Cashu is an ecash protocol where tokens +//! are issued by a mint and can be used for instant, private payments. +//! +//! # Configuration +//! +//! Use [`CashuConfig`] to specify: +//! - `mint_url` – the Cashu mint to connect to +//! - `unit` – the currency unit (typically `CurrencyUnit::Sat`) +//! - `npubcash_url` – optional [npub.cash](https://npub.cash) URL for Lightning address support +//! +//! # Feature flag +//! +//! This module is only available when the `cashu` feature is enabled (disabled by default). +//! Enable it in your `Cargo.toml`: +//! +//! ```toml +//! [dependencies] +//! orange-sdk = { version = "0.1", features = ["cashu"] } +//! ``` use crate::bitcoin::hex::DisplayHex; use crate::logging::Logger; @@ -45,14 +66,30 @@ pub mod cashu_store; use cashu_store::{CashuKvDatabase, read_has_recovered, write_has_recovered}; -/// Configuration for the Cashu wallet +/// Configuration for the Cashu ecash wallet backend. +/// +/// # Example +/// +/// ```rust,no_run +/// use orange_sdk::trusted_wallet::cashu::CashuConfig; +/// use orange_sdk::CurrencyUnit; +/// +/// let config = CashuConfig { +/// mint_url: "https://mint.example.com".to_string(), +/// unit: CurrencyUnit::Sat, +/// npubcash_url: Some("https://npub.cash".to_string()), +/// }; +/// ``` #[derive(Debug, Clone)] pub struct CashuConfig { - /// The mint URL to connect to + /// The URL of the Cashu mint to connect to (e.g. `https://mint.example.com`). pub mint_url: String, - /// The currency unit to use (typically Sat) + /// The currency unit for ecash tokens (typically [`CurrencyUnit::Sat`]). pub unit: CurrencyUnit, - /// Optional npub.cash URL for lightning address support (e.g., `https://npubx.cash`) + /// Optional [npub.cash](https://npub.cash) URL for Lightning address support. + /// + /// When set, the wallet can register and receive payments via a Lightning address + /// backed by the npub.cash service. pub npubcash_url: Option, } diff --git a/orange-sdk/src/trusted_wallet/mod.rs b/orange-sdk/src/trusted_wallet/mod.rs index 5f76082..84fd514 100644 --- a/orange-sdk/src/trusted_wallet/mod.rs +++ b/orange-sdk/src/trusted_wallet/mod.rs @@ -1,4 +1,18 @@ -//! This module defines the `TrustedWalletInterface` trait and its associated types. +//! Trusted wallet backends for the graduated custody model. +//! +//! This module defines the [`TrustedWalletInterface`] trait and provides concrete +//! implementations for different custodial backends: +//! +//! - **`spark`** (feature `spark`, enabled by default) – Uses the [Breez Spark SDK](https://breez.technology) +//! for custodial Lightning payments with low fees and instant settlement. +//! - **`cashu`** (feature `cashu`) – Uses the [Cashu Development Kit (CDK)](https://docs.rs/cdk) +//! for ecash-based custody via a Cashu mint. Supports [npub.cash](https://npub.cash) for +//! Lightning address integration. +//! - **`dummy`** (feature `_test-utils`) – A test-only in-memory implementation. +//! +//! The trusted wallet holds small balances for instant, low-fee payments while the +//! [`Wallet`](crate::Wallet) automatically moves larger amounts into self-custodial +//! Lightning channels via the rebalancer. use crate::store::TxStatus; @@ -43,7 +57,15 @@ pub struct Payment { pub(crate) type DynTrustedWalletInterface = dyn TrustedWalletInterface + Send + Sync; -/// Represents a trait for a trusted wallet interface. +/// The interface that all trusted wallet backends must implement. +/// +/// This trait is **sealed** — it cannot be implemented outside of this crate. The available +/// implementations are `Spark` (feature `spark`), `Cashu` (feature `cashu`), and +/// `DummyTrustedWallet` (feature `_test-utils`, test-only). +/// +/// Users don't interact with this trait directly. Instead, choose a backend via +/// [`ExtraConfig`] when building a [`WalletConfig`](crate::WalletConfig), and the +/// [`Wallet`](crate::Wallet) handles dispatching internally. pub trait TrustedWalletInterface: Send + Sync + private::Sealed { /// Returns the current balance of the wallet. fn get_balance( @@ -134,16 +156,23 @@ impl graduated_rebalancer::TrustedWallet for } } +/// Selects and configures the trusted wallet backend. +/// +/// Pass one of these variants in [`WalletConfig::extra_config`](crate::WalletConfig::extra_config) +/// to choose which custodial backend the wallet uses: +/// +/// - `Spark` – Breez Spark SDK (requires feature `spark`, enabled by default) +/// - `Cashu` – Cashu ecash via CDK (requires feature `cashu`) +/// - `Dummy` – in-memory test backend (requires feature `_test-utils`) #[derive(Clone)] -/// Extra configuration needed for different types of wallets. pub enum ExtraConfig { - /// Configuration for Spark wallet. + /// Use the Spark backend. See [`SparkWalletConfig`](crate::SparkWalletConfig) for options. #[cfg(feature = "spark")] Spark(crate::SparkWalletConfig), - /// Configuration for Cashu wallet. + /// Use the Cashu ecash backend. See [`CashuConfig`](cashu::CashuConfig) for options. #[cfg(feature = "cashu")] Cashu(cashu::CashuConfig), - /// Configuration for dummy wallet (test-only). + /// Use the in-memory dummy backend (test-only). #[cfg(feature = "_test-utils")] Dummy(dummy::DummyTrustedWalletExtraConfig), } @@ -164,22 +193,26 @@ mod private { impl Sealed for super::dummy::DummyTrustedWallet {} } -/// An error type for the Spark wallet implementation. +/// Errors from trusted wallet backend operations. +/// +/// Any of the trusted backends (Spark, Cashu, Dummy) may return these errors. They are +/// surfaced to the caller as [`WalletError::TrustedFailure`](crate::WalletError::TrustedFailure) +/// or [`InitFailure::TrustedFailure`](crate::InitFailure::TrustedFailure). #[derive(Debug)] pub enum TrustedError { - /// Not enough funds to complete the operation. + /// The wallet does not have enough funds to complete the operation. InsufficientFunds, - /// The wallet operation failed with a specific message. + /// A backend-specific operation failed. WalletOperationFailed(String), - /// The provided network is invalid. + /// The configured Bitcoin network does not match the backend's network. InvalidNetwork, - /// The spark wallet does not yet support the operation. + /// The requested operation is not supported by this backend. UnsupportedOperation(String), - /// Failed to convert an amount. + /// An amount conversion error (e.g. overflow or invalid unit). AmountError, - /// An I/O error occurred. + /// An I/O error occurred during a storage or network operation. IOError(ldk_node::lightning::io::Error), - /// An unspecified error occurred. + /// An unspecified error with a descriptive message. Other(String), } diff --git a/orange-sdk/src/trusted_wallet/spark/mod.rs b/orange-sdk/src/trusted_wallet/spark/mod.rs index 5010599..3150968 100644 --- a/orange-sdk/src/trusted_wallet/spark/mod.rs +++ b/orange-sdk/src/trusted_wallet/spark/mod.rs @@ -1,4 +1,18 @@ -//! An implementation of `TrustedWalletInterface` using the Spark SDK. +//! Spark trusted wallet backend. +//! +//! This module implements [`TrustedWalletInterface`] using the +//! [Breez Spark SDK](https://breez.technology). Spark provides custodial Lightning payments +//! with instant settlement and low fees. +//! +//! # Configuration +//! +//! Use [`SparkWalletConfig`] to control sync frequency, payment preferences, and Lightning +//! address domain. The default configuration syncs every 60 seconds and prefers Lightning +//! over Spark-native payments. +//! +//! # Feature flag +//! +//! This module is only available when the `spark` feature is enabled (enabled by default). pub(crate) mod spark_store; @@ -37,17 +51,39 @@ use std::sync::Arc; use std::time::Duration; use uuid::Uuid; -/// Configuration options for the Spark wallet. +/// Configuration for the Spark trusted wallet backend. +/// +/// All fields have reasonable defaults via the [`Default`] implementation. +/// +/// # Example +/// +/// ```rust,no_run +/// use orange_sdk::trusted_wallet::spark::SparkWalletConfig; +/// +/// // Use defaults (60s sync, Lightning-preferred, breez.tips domain) +/// let config = SparkWalletConfig::default(); +/// +/// // Or customize +/// let config = SparkWalletConfig { +/// sync_interval_secs: 30, +/// prefer_spark_over_lightning: true, +/// lnurl_domain: Some("mydomain.com".to_string()), +/// }; +/// ``` #[derive(Debug, Clone)] pub struct SparkWalletConfig { - /// How often to sync the wallet with the blockchain, in seconds. - /// Default is 60 seconds. + /// How often to sync the wallet with the Spark backend, in seconds. + /// + /// Default: `60`. pub sync_interval_secs: u32, - /// When this is set to `true` we will prefer to use spark payments over - /// lightning when sending and receiving. This has the benefit of lower fees - /// but is at the cost of privacy. + /// When `true`, prefer Spark-native payments over standard Lightning. + /// + /// Spark payments have lower fees but less privacy than Lightning. + /// Default: `false`. pub prefer_spark_over_lightning: bool, - /// The domain used for receiving through lnurl-pay and lightning address. + /// The domain used for LNURL-pay and Lightning address receiving. + /// + /// Default: `Some("breez.tips")`. pub lnurl_domain: Option, }