diff --git a/examples/cli/src/main.rs b/examples/cli/src/main.rs index 0f71498..3d52536 100644 --- a/examples/cli/src/main.rs +++ b/examples/cli/src/main.rs @@ -56,6 +56,13 @@ enum Commands { /// Amount in sats (optional) amount: Option, }, + /// Get the current lightning address + GetLightningAddress, + /// Register a lightning address + RegisterLightningAddress { + /// The lightning address name to register + name: String, + }, /// Clear the screen Clear, /// Exit the application @@ -367,6 +374,14 @@ fn parse_command(input: &str) -> Result { Ok(Commands::EstimateFee { destination, amount }) }, + "get-lightning-address" | "get-ln-addr" | "ln-addr" => Ok(Commands::GetLightningAddress), + "register-lightning-address" | "register-ln-addr" => { + if parts.len() < 2 { + return Err(anyhow::anyhow!("Usage: register-lightning-address ")); + } + let name = parts[1].to_string(); + Ok(Commands::RegisterLightningAddress { name }) + }, "clear" | "cls" => Ok(Commands::Clear), "exit" | "quit" | "q" => Ok(Commands::Exit), "help" => { @@ -590,6 +605,60 @@ async fn execute_command(command: Commands, state: &mut WalletState) -> Result<( }, } }, + Commands::GetLightningAddress => { + let wallet = state.wallet(); + + println!("{} Fetching lightning address...", "⚡".bright_yellow()); + + match wallet.get_lightning_address().await { + Ok(Some(address)) => { + println!( + "{} Lightning address: {}", + "⚡".bright_green(), + address.bright_cyan() + ); + }, + Ok(None) => { + println!("{} No lightning address registered yet.", "⚡".bright_yellow()); + println!( + "{} Use 'register-lightning-address ' to register one", + "Hint:".bright_yellow().bold() + ); + }, + Err(e) => { + return Err(anyhow::anyhow!("Failed to get lightning address: {:?}", e)); + }, + } + }, + Commands::RegisterLightningAddress { name } => { + let wallet = state.wallet(); + + println!( + "{} Registering lightning address: {}...", + "⚡".bright_yellow(), + name.bright_cyan() + ); + + match wallet.register_lightning_address(name.clone()).await { + Ok(()) => { + println!("{} Lightning address registered successfully!", "✅".bright_green()); + // Fetch and display the full address + match wallet.get_lightning_address().await { + Ok(Some(address)) => { + println!( + "{} Your lightning address: {}", + "⚡".bright_green(), + address.bright_cyan() + ); + }, + _ => {}, + } + }, + Err(e) => { + return Err(anyhow::anyhow!("Failed to register lightning address: {:?}", e)); + }, + } + }, Commands::Clear => { print!("\x1B[2J\x1B[1;1H"); std::io::stdout().flush().unwrap(); @@ -628,6 +697,12 @@ fn print_help() { println!(" {} [amount]", "estimate-fee".bright_green().bold()); println!(" Estimate the fee for a payment"); println!(); + println!(" {}", "get-lightning-address".bright_green().bold()); + println!(" Get the current lightning address"); + println!(); + println!(" {} ", "register-lightning-address".bright_green().bold()); + println!(" Register a lightning address"); + println!(); println!(" {}", "clear".bright_green().bold()); println!(" Clear the terminal screen"); println!(); diff --git a/orange-sdk/src/ffi/orange/wallet.rs b/orange-sdk/src/ffi/orange/wallet.rs index fe17635..1524009 100644 --- a/orange-sdk/src/ffi/orange/wallet.rs +++ b/orange-sdk/src/ffi/orange/wallet.rs @@ -251,4 +251,16 @@ impl Wallet { pub fn event_handled(&self) -> bool { self.inner.event_handled().is_ok() } + + /// Gets the lightning address for this wallet, if one is set. + pub async fn get_lightning_address(&self) -> Result, WalletError> { + let result = self.inner.get_lightning_address().await?; + Ok(result) + } + + /// Attempts to register the lightning address for this wallet. + pub async fn register_lightning_address(&self, name: String) -> Result<(), WalletError> { + self.inner.register_lightning_address(name).await?; + Ok(()) + } } diff --git a/orange-sdk/src/lib.rs b/orange-sdk/src/lib.rs index be69f95..5fba899 100644 --- a/orange-sdk/src/lib.rs +++ b/orange-sdk/src/lib.rs @@ -533,7 +533,7 @@ impl Wallet { ExtraConfig::Spark(sp) => Arc::new(Box::new( Spark::init( &config, - *sp, + sp.clone(), Arc::clone(&store), Arc::clone(&event_queue), tx_metadata.clone(), @@ -1345,6 +1345,16 @@ impl Wallet { res } + /// Gets the lightning address for this wallet, if one is set. + 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. + 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. pub async fn stop(&self) { diff --git a/orange-sdk/src/trusted_wallet/cashu/mod.rs b/orange-sdk/src/trusted_wallet/cashu/mod.rs index b1053c0..788f4f8 100644 --- a/orange-sdk/src/trusted_wallet/cashu/mod.rs +++ b/orange-sdk/src/trusted_wallet/cashu/mod.rs @@ -469,6 +469,22 @@ impl TrustedWalletInterface for Cashu { }) } + fn get_lightning_address( + &self, + ) -> Pin, TrustedError>> + Send + '_>> { + Box::pin(async { Ok(None) }) + } + + fn register_lightning_address( + &self, _name: String, + ) -> Pin> + Send + '_>> { + Box::pin(async { + Err(TrustedError::UnsupportedOperation( + "register_lightning_address is not supported in Cashu Wallet".to_string(), + )) + }) + } + fn stop(&self) -> Pin + Send + '_>> { Box::pin(async move { log_info!(self.logger, "Stopping Cashu wallet"); diff --git a/orange-sdk/src/trusted_wallet/dummy.rs b/orange-sdk/src/trusted_wallet/dummy.rs index 0dcd527..91f7950 100644 --- a/orange-sdk/src/trusted_wallet/dummy.rs +++ b/orange-sdk/src/trusted_wallet/dummy.rs @@ -411,6 +411,22 @@ impl TrustedWalletInterface for DummyTrustedWallet { }) } + fn get_lightning_address( + &self, + ) -> Pin, TrustedError>> + Send + '_>> { + Box::pin(async { Ok(None) }) + } + + fn register_lightning_address( + &self, _name: String, + ) -> Pin> + Send + '_>> { + Box::pin(async { + Err(TrustedError::UnsupportedOperation( + "register_lightning_address is not supported in DummyTrustedWallet".to_string(), + )) + }) + } + fn stop(&self) -> Pin + Send + '_>> { Box::pin(async move { let _ = self.ldk_node.stop(); diff --git a/orange-sdk/src/trusted_wallet/mod.rs b/orange-sdk/src/trusted_wallet/mod.rs index 19d9874..5f76082 100644 --- a/orange-sdk/src/trusted_wallet/mod.rs +++ b/orange-sdk/src/trusted_wallet/mod.rs @@ -89,6 +89,16 @@ pub trait TrustedWalletInterface: Send + Sync + private::Sealed { &self, payment_hash: [u8; 32], ) -> Pin> + Send + '_>>; + /// Gets the lightning address for this wallet, if one is set. + fn get_lightning_address( + &self, + ) -> Pin, TrustedError>> + Send + '_>>; + + /// Attempts to register the lightning address for this wallet. + fn register_lightning_address( + &self, name: String, + ) -> Pin> + Send + '_>>; + /// Stops the wallet, cleaning up any resources. /// This is typically used to gracefully shut down the wallet. fn stop(&self) -> Pin + Send + '_>>; diff --git a/orange-sdk/src/trusted_wallet/spark/mod.rs b/orange-sdk/src/trusted_wallet/spark/mod.rs index 2c7f0b1..2000354 100644 --- a/orange-sdk/src/trusted_wallet/spark/mod.rs +++ b/orange-sdk/src/trusted_wallet/spark/mod.rs @@ -21,7 +21,8 @@ use bitcoin_payment_instructions::amount::Amount; use breez_sdk_spark::{ BreezSdk, EventListener, GetInfoRequest, ListPaymentsRequest, PaymentDetails, PaymentStatus, PaymentType, PrepareSendPaymentRequest, ReceivePaymentMethod, ReceivePaymentRequest, - SdkBuilder, SdkError, SdkEvent, SendPaymentMethod, SendPaymentRequest, + RegisterLightningAddressRequest, SdkBuilder, SdkError, SdkEvent, SendPaymentMethod, + SendPaymentRequest, }; use graduated_rebalancer::ReceivedLightningPayment; @@ -37,7 +38,7 @@ use std::time::Duration; use uuid::Uuid; /// Configuration options for the Spark wallet. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Clone)] pub struct SparkWalletConfig { /// How often to sync the wallet with the blockchain, in seconds. /// Default is 60 seconds. @@ -46,11 +47,17 @@ pub struct SparkWalletConfig { /// lightning when sending and receiving. This has the benefit of lower fees /// but is at the cost of privacy. pub prefer_spark_over_lightning: bool, + /// The domain used for receiving through lnurl-pay and lightning address. + pub lnurl_domain: Option, } impl Default for SparkWalletConfig { fn default() -> Self { - SparkWalletConfig { sync_interval_secs: 60, prefer_spark_over_lightning: false } + SparkWalletConfig { + sync_interval_secs: 60, + prefer_spark_over_lightning: false, + lnurl_domain: Some("breez.tips".to_string()), + } } } @@ -59,7 +66,7 @@ impl Default for SparkWalletConfig { const BREEZ_API_KEY: &str = "MIIBajCCARygAwIBAgIHPnfOjAhBgzAFBgMrZXAwEDEOMAwGA1UEAxMFQnJlZXowHhcNMjUwOTE5MjEzNTU1WhcNMzUwOTE3MjEzNTU1WjAqMRMwEQYDVQQKEwpvcmFuZ2Utc2RrMRMwEQYDVQQDEwpvcmFuZ2Utc2RrMCowBQYDK2VwAyEA0IP1y98gPByiIMoph1P0G6cctLb864rNXw1LRLOpXXejezB5MA4GA1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTaOaPuXmtLDTJVv++VYBiQr9gHCTAfBgNVHSMEGDAWgBTeqtaSVvON53SSFvxMtiCyayiYazAZBgNVHREEEjAQgQ5iZW5Ac3BpcmFsLnh5ejAFBgMrZXADQQCry+1LkA3nrYa1sovS5iFI1Tkpmr/R0nM/4gJtsO93vFOkm3vBEGwjKAV7lrGzFcFbbuyM1wEJPi4Po1XCEG0D"; impl SparkWalletConfig { - fn to_breez_config(self, network: Network) -> Result { + fn into_breez_config(self, network: Network) -> Result { let network = match network { Network::Bitcoin => breez_sdk_spark::Network::Mainnet, Network::Regtest => breez_sdk_spark::Network::Regtest, @@ -75,7 +82,7 @@ impl SparkWalletConfig { real_time_sync_server_url: None, api_key: Some(BREEZ_API_KEY.to_string()), max_deposit_claim_fee: None, - lnurl_domain: None, + lnurl_domain: self.lnurl_domain, private_enabled_default: true, }) } @@ -249,6 +256,34 @@ impl TrustedWalletInterface for Spark { }) } + fn get_lightning_address( + &self, + ) -> Pin, TrustedError>> + Send + '_>> { + Box::pin(async move { + match self.spark_wallet.get_lightning_address().await? { + None => Ok(None), + Some(addr) => Ok(Some(addr.lightning_address)), + } + }) + } + + fn register_lightning_address( + &self, name: String, + ) -> Pin> + Send + '_>> { + Box::pin(async move { + let res = self.get_lightning_address().await?; + if res.is_some() { + return Err(TrustedError::Other( + "Wallet already has a lightning address".to_string(), + )); + } + + let params = RegisterLightningAddressRequest { username: name, description: None }; + self.spark_wallet.register_lightning_address(params).await?; + Ok(()) + }) + } + fn stop(&self) -> Pin + Send + '_>> { Box::pin(async move { log_info!(self.logger, "Stopping Spark wallet"); @@ -264,7 +299,8 @@ impl Spark { event_queue: Arc, tx_metadata: TxMetadataStore, logger: Arc, runtime: Arc, ) -> Result { - let spark_config: breez_sdk_spark::Config = spark_config.to_breez_config(config.network)?; + let spark_config: breez_sdk_spark::Config = + spark_config.into_breez_config(config.network)?; let seed = match &config.seed { Seed::Seed64(bytes) => breez_sdk_spark::Seed::Entropy(bytes.to_vec()),