diff --git a/orange-sdk/src/builder.rs b/orange-sdk/src/builder.rs deleted file mode 100644 index 800c257..0000000 --- a/orange-sdk/src/builder.rs +++ /dev/null @@ -1,380 +0,0 @@ -//! Builder for constructing [`Wallet`] instances with a fluent API. - -#[cfg(feature = "spark")] -use crate::trusted_wallet::TrustedError; -use crate::{ - ChainSource, ExtraConfig, InitFailure, Seed, StorageConfig, Tunables, Wallet, WalletConfig, -}; - -use ldk_node::bitcoin::secp256k1::PublicKey; -use ldk_node::bitcoin::{Network, io}; -use ldk_node::lightning::ln::msgs::SocketAddress; -use std::fmt; -use std::path::PathBuf; -use std::sync::Arc; -use tokio::runtime::Runtime; - -/// Represents possible errors during wallet builder configuration. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum BuilderError { - /// Storage configuration was not provided. - MissingStorageConfig, - /// Log file path was not provided. - MissingLogFile, - /// Chain source configuration was not provided. - MissingChainSource, - /// LSP configuration was not provided. - MissingLsp, - /// Network configuration was not provided. - MissingNetwork, - /// Seed was not provided. - MissingSeed, -} - -impl fmt::Display for BuilderError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - BuilderError::MissingStorageConfig => write!(f, "Storage configuration is required"), - BuilderError::MissingLogFile => write!(f, "Log file path is required"), - BuilderError::MissingChainSource => write!(f, "Chain source configuration is required"), - BuilderError::MissingLsp => write!(f, "LSP configuration is required"), - BuilderError::MissingNetwork => write!(f, "Network configuration is required"), - BuilderError::MissingSeed => write!(f, "Seed is required"), - } - } -} - -impl std::error::Error for BuilderError {} - -impl From for InitFailure { - fn from(e: BuilderError) -> InitFailure { - InitFailure::BuildError(e) - } -} - -/// Builder for constructing [`Wallet`] instances. -/// -/// Provides a fluent API for setting up wallet configuration with sensible defaults. -/// -/// # Example -/// -/// ```rust,no_run -/// use orange_sdk::{WalletBuilder, Seed, ChainSource, StorageConfig}; -/// use orange_sdk::trusted_wallet::dummy::{DummyTrustedWalletExtraConfig}; -/// use orange_sdk::bitcoin::Network; -/// use ldk_node::bip39::Mnemonic; -/// use ldk_node::lightning::ln::msgs::SocketAddress; -/// use std::sync::Arc; -/// use uuid::Uuid; -/// -/// # async fn example() -> Result<(), orange_sdk::InitFailure> { -/// let runtime = Arc::new(tokio::runtime::Runtime::new().unwrap()); -/// let mnemonic = Mnemonic::from_entropy(&[0; 16]).unwrap(); // Actual entropy should be random -/// let seed = Seed::Mnemonic { mnemonic, passphrase: None }; -/// let node_id = "02f7467f4de732f3b3cffc8d5e007aecdf6e58878edb6e46a8e80164421c1b90aa".parse().unwrap(); -/// let socket_addr = SocketAddress::TcpIpV4 { addr: [127, 0, 0, 1], port: 9735 }; -/// -/// // Note: In practice, you'd need actual LSP and Bitcoin node instances for the wallet to function. -/// let wallet = WalletBuilder::new() -/// .seed(seed) -/// .network(Network::Bitcoin) -/// .storage_config(StorageConfig::LocalSQLite("/path/to/wallet.db".to_string())) -/// .log_file("/path/to/wallet.log".into()) -/// .chain_source(ChainSource::Electrum("ssl://electrum.blockstream.info:60002".to_string())) -/// .lsp(node_id, socket_addr, None) -/// .build_spark(None) -/// .await?; -/// # Ok(()) -/// # } -/// ``` -#[derive(Default)] -pub struct WalletBuilder { - runtime: Option>, - storage_config: Option, - log_file: Option, - chain_source: Option, - lsp: Option<(SocketAddress, PublicKey, Option)>, - scorer_url: Option, - network: Option, - seed: Option, - tunables: Option, -} - -impl WalletBuilder { - /// Creates a new [`WalletBuilder`] with the provided tokio runtime. - /// - /// # Arguments - /// - /// * `runtime` - An Arc-wrapped tokio Runtime for async operations - pub fn new() -> Self { - Self { - runtime: None, - storage_config: None, - log_file: None, - chain_source: None, - lsp: None, - scorer_url: None, - network: None, - seed: None, - tunables: None, - } - } - - /// Sets a custom tokio runtime for the wallet. - /// - /// # Arguments - /// - /// * `rt` - A tokio Runtime instance to be used by the wallet - pub fn runtime(mut self, rt: Arc) -> Self { - self.runtime = Some(rt); - self - } - - /// Sets the storage configuration for the wallet. - /// - /// # Arguments - /// - /// * `storage_config` - Configuration for wallet storage (local SQLite or VSS) - pub fn storage_config(mut self, storage_config: StorageConfig) -> Self { - self.storage_config = Some(storage_config); - self - } - - /// Sets the log file path for the wallet. - /// - /// # Arguments - /// - /// * `log_file` - Path where wallet logs will be written - pub fn log_file(mut self, log_file: PathBuf) -> Self { - self.log_file = Some(log_file); - self - } - - /// Sets the blockchain data source for the wallet. - /// - /// # Arguments - /// - /// * `chain_source` - Configuration for blockchain data source (Electrum, Esplora, or Bitcoin Core RPC) - pub fn chain_source(mut self, chain_source: ChainSource) -> Self { - self.chain_source = Some(chain_source); - self - } - - /// Sets the Lightning Service Provider (LSP) configuration. - /// - /// # Arguments - /// - /// * `node_id` - The LSP node's public key - /// * `address` - The network address to connect to the LSP - /// * `auth_token` - Optional authentication token for the LSP - pub fn lsp( - mut self, node_id: PublicKey, address: SocketAddress, auth_token: Option, - ) -> Self { - self.lsp = Some((address, node_id, auth_token)); - self - } - - /// Sets the scorer URL for lightning routing optimization. - /// - /// # Arguments - /// - /// * `scorer_url` - URL to download route scorer data from - pub fn scorer_url(mut self, scorer_url: String) -> Self { - self.scorer_url = Some(scorer_url); - self - } - - /// Sets the Bitcoin network for the wallet. - /// - /// # Arguments - /// - /// * `network` - Bitcoin network (Mainnet, Testnet, Signet, or Regtest) - pub fn network(mut self, network: Network) -> Self { - self.network = Some(network); - self - } - - /// Sets the seed for wallet generation. - /// - /// # Arguments - /// - /// * `seed` - Seed for deterministic wallet generation (mnemonic or 64-byte seed) - pub fn seed(mut self, seed: Seed) -> Self { - self.seed = Some(seed); - self - } - - /// Sets the tunables for wallet behavior configuration. - /// - /// # Arguments - /// - /// * `tunables` - Configuration parameters controlling wallet behavior - pub fn tunables(mut self, tunables: Tunables) -> Self { - self.tunables = Some(tunables); - self - } - - /// Builds and initializes the wallet with the configured parameters. - /// - /// # Type Parameters - /// - /// * `T` - The trusted wallet implementation type - /// - /// # Arguments - /// - /// * `extra_config` - Implementation-specific configuration for the trusted wallet - /// - /// # Returns - /// - /// Returns a [`Result`] containing the initialized [`Wallet`] on success, or an [`InitFailure`] on error. - /// - /// # Errors - /// - /// This method will return an error if: - /// - Required configuration is missing - /// - Storage initialization fails - /// - Network connection fails - /// - Trusted wallet initialization fails - /// - LDK node fails to start - pub async fn build(self, extra_config: ExtraConfig) -> Result { - let config = WalletConfig { - storage_config: self.storage_config.ok_or(BuilderError::MissingStorageConfig)?, - log_file: self.log_file.ok_or(BuilderError::MissingLogFile)?, - chain_source: self.chain_source.ok_or(BuilderError::MissingChainSource)?, - lsp: self.lsp.ok_or(BuilderError::MissingLsp)?, - scorer_url: self.scorer_url, - network: self.network.ok_or(BuilderError::MissingNetwork)?, - seed: self.seed.ok_or(BuilderError::MissingSeed)?, - tunables: self.tunables.unwrap_or_default(), - extra_config, - }; - - let runtime = match self.runtime { - Some(runtime) => runtime, - None => { - let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().map_err( - |_| { - InitFailure::IoError(io::Error::new( - io::ErrorKind::Other, - "Failed to create tokio runtime", - )) - }, - )?; - Arc::new(rt) - }, - }; - - Wallet::new(runtime, config).await - } - - /// Builds and initializes the wallet with the configured parameters for Spark wallet. - /// - /// # Type Parameters - /// - /// * `T` - The trusted wallet implementation type - /// - /// # Returns - /// - /// Returns a [`Result`] containing the initialized [`Wallet`] on success, or an [`InitFailure`] on error. - /// - /// # Errors - /// - /// This method will return an error if: - /// - Required configuration is missing - /// - Storage initialization fails - /// - Network connection fails - /// - Trusted wallet initialization fails - /// - LDK node fails to start - #[cfg(feature = "spark")] - pub async fn build_spark( - self, spark_config: Option, - ) -> Result { - let spark_config = match spark_config { - Some(cfg) => cfg, - None => { - let network = self.network.ok_or(BuilderError::MissingNetwork)?; - spark_wallet::SparkWalletConfig::default_config( - network - .try_into() - .map_err(|_| InitFailure::TrustedFailure(TrustedError::InvalidNetwork))?, - ) - }, - }; - - let runtime = match self.runtime { - Some(runtime) => runtime, - None => { - let rt = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .map_err(|e| InitFailure::IoError(e.into()))?; - Arc::new(rt) - }, - }; - - let config = WalletConfig { - storage_config: self.storage_config.ok_or(BuilderError::MissingStorageConfig)?, - log_file: self.log_file.ok_or(BuilderError::MissingLogFile)?, - chain_source: self.chain_source.ok_or(BuilderError::MissingChainSource)?, - lsp: self.lsp.ok_or(BuilderError::MissingLsp)?, - scorer_url: self.scorer_url, - network: self.network.ok_or(BuilderError::MissingNetwork)?, - seed: self.seed.ok_or(BuilderError::MissingSeed)?, - tunables: self.tunables.unwrap_or_default(), - extra_config: ExtraConfig::Spark(spark_config), - }; - - Wallet::new(runtime, config).await - } - - /// Builds and initializes the wallet with the configured parameters for Cashu wallet. - /// - /// # Type Parameters - /// - /// * `T` - The trusted wallet implementation type - /// - /// # Returns - /// - /// Returns a [`Result`] containing the initialized [`Wallet`] on success, or an [`InitFailure`] on error. - /// - /// # Errors - /// - /// This method will return an error if: - /// - Required configuration is missing - /// - Storage initialization fails - /// - Network connection fails - /// - Trusted wallet initialization fails - /// - LDK node fails to start - #[cfg(feature = "cashu")] - pub async fn build_cashu( - self, mint_url: String, unit: Option, - ) -> Result { - let cashu_config = - crate::CashuConfig { mint_url, unit: unit.unwrap_or(cdk::nuts::CurrencyUnit::Sat) }; - - let runtime = match self.runtime { - Some(runtime) => runtime, - None => { - let rt = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .map_err(|e| InitFailure::IoError(e.into()))?; - Arc::new(rt) - }, - }; - - let config = WalletConfig { - storage_config: self.storage_config.ok_or(BuilderError::MissingStorageConfig)?, - log_file: self.log_file.ok_or(BuilderError::MissingLogFile)?, - chain_source: self.chain_source.ok_or(BuilderError::MissingChainSource)?, - lsp: self.lsp.ok_or(BuilderError::MissingLsp)?, - scorer_url: self.scorer_url, - network: self.network.ok_or(BuilderError::MissingNetwork)?, - seed: self.seed.ok_or(BuilderError::MissingSeed)?, - tunables: self.tunables.unwrap_or_default(), - extra_config: ExtraConfig::Cashu(cashu_config), - }; - - Wallet::new(runtime, config).await - } -} diff --git a/orange-sdk/src/lib.rs b/orange-sdk/src/lib.rs index e99eb29..bca019d 100644 --- a/orange-sdk/src/lib.rs +++ b/orange-sdk/src/lib.rs @@ -50,7 +50,6 @@ use std::path::PathBuf; use std::sync::Arc; use std::time::{Duration, SystemTime}; -pub mod builder; mod event; mod lightning_wallet; pub(crate) mod logging; @@ -65,7 +64,6 @@ use trusted_wallet::TrustedError; #[cfg(feature = "cashu")] pub use crate::trusted_wallet::cashu::CashuConfig; pub use bitcoin_payment_instructions; -pub use builder::{BuilderError, WalletBuilder}; #[cfg(feature = "cashu")] pub use cdk::nuts::nut00::CurrencyUnit; pub use event::{Event, EventQueue}; @@ -386,8 +384,6 @@ impl PaymentInfo { /// Represents possible failures during wallet initialization. #[derive(Debug)] pub enum InitFailure { - /// Failure to build the wallet using the builder pattern. - BuildError(BuilderError), /// I/O error during initialization. IoError(io::Error), /// Failure to build the LDK node. @@ -482,12 +478,23 @@ impl fmt::Display for SingleUseReceiveUri { } impl Wallet { - /// Constructs a new Wallet. + /// Constructs a new Wallet + pub async fn new(config: WalletConfig) -> Result { + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .map_err(|e| InitFailure::IoError(e.into()))?; + + Self::new_with_runtime(Arc::new(rt), config).await + } + /// Constructs a new Wallet with a runtime. /// /// `runtime` must be a reference to the running `tokio` runtime which we are currently /// operating in. // TODO: WOW that is a terrible API lol - pub async fn new(runtime: Arc, config: WalletConfig) -> Result { + pub async fn new_with_runtime( + runtime: Arc, config: WalletConfig, + ) -> Result { let tunables = config.tunables; let network = config.network; let logger = Arc::new(Logger::new(&config.log_file).expect("Failed to open log file")); diff --git a/orange-sdk/tests/test_utils.rs b/orange-sdk/tests/test_utils.rs index 13b9efc..8b8aba2 100644 --- a/orange-sdk/tests/test_utils.rs +++ b/orange-sdk/tests/test_utils.rs @@ -16,7 +16,7 @@ use ldk_node::payment::PaymentStatus; use ldk_node::{Node, bitcoin}; #[cfg(not(feature = "_cashu-tests"))] use orange_sdk::trusted_wallet::dummy::DummyTrustedWalletExtraConfig; -use orange_sdk::{ChainSource, ExtraConfig, Seed, StorageConfig, WalletBuilder}; +use orange_sdk::{ChainSource, ExtraConfig, Seed, StorageConfig, Tunables, Wallet, WalletConfig}; use rand::RngCore; use std::env::temp_dir; use std::future::Future; @@ -253,24 +253,24 @@ pub fn build_test_nodes() -> TestParams { let rt_clone = Arc::clone(&rt); let bitcoind_port = bitcoind.params.rpc_socket.port(); - rt.block_on(async move { - WalletBuilder::new() - .runtime(rt_clone) - .storage_config(StorageConfig::LocalSQLite(tmp.to_str().unwrap().to_string())) - .log_file(tmp.join("orange.log")) - .chain_source(ChainSource::BitcoindRPC { - host: "127.0.0.1".to_string(), - port: bitcoind_port, - user: cookie.user, - password: cookie.password, - }) - .lsp(lsp_node_id, lsp_listen, None) - .network(Network::Regtest) - .seed(Seed::Seed64(seed)) - .build(ExtraConfig::Dummy(dummy_wallet_config)) - .await - .unwrap() - }) + + let wallet_config = WalletConfig { + storage_config: StorageConfig::LocalSQLite(tmp.to_str().unwrap().to_string()), + log_file: tmp.join("orange.log"), + scorer_url: None, + tunables: Tunables::default(), + chain_source: ChainSource::BitcoindRPC { + host: "127.0.0.1".to_string(), + port: bitcoind_port, + user: cookie.user, + password: cookie.password, + }, + lsp: (lsp_listen, lsp_node_id, None), + network: Network::Regtest, + seed: Seed::Seed64(seed), + extra_config: ExtraConfig::Dummy(dummy_wallet_config), + }; + rt.block_on(async move { Wallet::new_with_runtime(rt_clone, wallet_config).await.unwrap() }) }; #[cfg(feature = "_cashu-tests")] @@ -378,28 +378,31 @@ pub fn build_test_nodes() -> TestParams { }); let rt_clone = Arc::clone(&rt); - let wallet = rt.block_on(async move { - let tmp = temp_dir().join(format!("orange-test-{test_id}/wallet")); - WalletBuilder::new() - .runtime(rt_clone) - .storage_config(StorageConfig::LocalSQLite(tmp.to_str().unwrap().to_string())) - .log_file(tmp.join("orange.log")) - .chain_source(ChainSource::BitcoindRPC { - host: "127.0.0.1".to_string(), - port: bitcoind_port, - user: cookie.user, - password: cookie.password, - }) - .lsp(lsp_node_id, lsp_listen, None) - .network(Network::Regtest) - .seed(Seed::Seed64(seed)) - .build(ExtraConfig::Cashu(orange_sdk::CashuConfig { - mint_url: format!("http://127.0.0.1:{}", mint_addr.port()), - unit: orange_sdk::CurrencyUnit::Sat, - })) - .await - .unwrap() - }); + + let tmp = temp_dir().join(format!("orange-test-{test_id}/wallet")); + let wallet_config = WalletConfig { + storage_config: StorageConfig::LocalSQLite(tmp.to_str().unwrap().to_string()), + log_file: tmp.join("orange.log"), + scorer_url: None, + tunables: Tunables::default(), + chain_source: ChainSource::BitcoindRPC { + host: "127.0.0.1".to_string(), + port: bitcoind_port, + user: cookie.user, + password: cookie.password, + }, + lsp: (lsp_listen, lsp_node_id, None), + network: Network::Regtest, + seed: Seed::Seed64(seed), + extra_config: ExtraConfig::Cashu(orange_sdk::CashuConfig { + mint_url: format!("http://127.0.0.1:{}", mint_addr.port()), + unit: orange_sdk::CurrencyUnit::Sat, + }), + }; + let wallet = + rt.block_on( + async move { Wallet::new_with_runtime(rt_clone, wallet_config).await.unwrap() }, + ); return TestParams { wallet, lsp, third_party, bitcoind, rt, _mint: mint }; };