Skip to content

Commit 6c4aa75

Browse files
benthecarmanclaude
andcommitted
Add lightning address support for Cashu via npub.cash
Implement get_lightning_address and register_lightning_address for the Cashu wallet using CDK's built-in npub.cash integration. When configured with a npubcash_url, the wallet enables npub.cash during init, starts background polling for incoming quotes, and returns deterministic lightning addresses derived from the wallet's Nostr keys. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ac19971 commit 6c4aa75

File tree

8 files changed

+188
-40
lines changed

8 files changed

+188
-40
lines changed

examples/cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ name = "orange-cli"
88
path = "src/main.rs"
99

1010
[dependencies]
11-
orange-sdk = { path = "../../orange-sdk" }
11+
orange-sdk = { path = "../../orange-sdk", features = ["cashu"] }
1212
tokio = { version = "1.0", features = ["full"] }
1313
clap = { version = "4.0", features = ["derive"] }
1414
anyhow = "1.0"

examples/cli/src/main.rs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use rustyline::error::ReadlineError;
66

77
use orange_sdk::bitcoin_payment_instructions::amount::Amount;
88
use orange_sdk::{
9-
ChainSource, Event, ExtraConfig, LoggerType, Mnemonic, PaymentInfo, Seed, SparkWalletConfig,
10-
StorageConfig, Tunables, Wallet, WalletConfig, bitcoin::Network,
9+
CashuConfig, ChainSource, CurrencyUnit, Event, ExtraConfig, LoggerType, Mnemonic, PaymentInfo,
10+
Seed, SparkWalletConfig, StorageConfig, Tunables, Wallet, WalletConfig, bitcoin::Network,
1111
};
1212
use rand::RngCore;
1313
use std::fs;
@@ -23,6 +23,15 @@ const NETWORK: Network = Network::Bitcoin; // Supports Bitcoin and Regtest
2323
#[derive(Parser)]
2424
#[command(author, version, about, long_about = None)]
2525
struct Cli {
26+
/// Use Cashu wallet instead of Spark
27+
#[arg(long)]
28+
cashu: bool,
29+
/// Cashu mint URL (requires --cashu)
30+
#[arg(long, requires = "cashu")]
31+
mint_url: String,
32+
/// npub.cash URL for lightning address support (requires --cashu)
33+
#[arg(long, requires = "cashu")]
34+
npubcash_url: Option<String>,
2635
#[command(subcommand)]
2736
command: Option<Commands>,
2837
}
@@ -74,12 +83,22 @@ struct WalletState {
7483
shutdown: Arc<AtomicBool>,
7584
}
7685

77-
fn get_config(network: Network) -> Result<WalletConfig> {
86+
fn get_config(network: Network, cli: &Cli) -> Result<WalletConfig> {
7887
let storage_path = format!("./wallet_data/{network}");
7988

8089
// Generate or load seed
8190
let seed = generate_or_load_seed(&storage_path)?;
8291

92+
let extra_config = if cli.cashu {
93+
ExtraConfig::Cashu(CashuConfig {
94+
mint_url: cli.mint_url.clone(),
95+
unit: CurrencyUnit::Sat,
96+
npubcash_url: cli.npubcash_url.clone(),
97+
})
98+
} else {
99+
ExtraConfig::Spark(SparkWalletConfig::default())
100+
};
101+
83102
match network {
84103
Network::Regtest => {
85104
let lsp_address = "185.150.162.100:3551"
@@ -103,7 +122,7 @@ fn get_config(network: Network) -> Result<WalletConfig> {
103122
network,
104123
seed,
105124
tunables: Tunables::default(),
106-
extra_config: ExtraConfig::Spark(SparkWalletConfig::default()),
125+
extra_config,
107126
})
108127
},
109128
Network::Bitcoin => {
@@ -132,17 +151,17 @@ fn get_config(network: Network) -> Result<WalletConfig> {
132151
network,
133152
seed,
134153
tunables: Tunables::default(),
135-
extra_config: ExtraConfig::Spark(SparkWalletConfig::default()),
154+
extra_config,
136155
})
137156
},
138157
_ => Err(anyhow::anyhow!("Unsupported network: {network:?}")),
139158
}
140159
}
141160

142161
impl WalletState {
143-
async fn new() -> Result<Self> {
162+
async fn new(cli: &Cli) -> Result<Self> {
144163
let shutdown = Arc::new(AtomicBool::new(false));
145-
let config = get_config(NETWORK)
164+
let config = get_config(NETWORK, cli)
146165
.with_context(|| format!("Failed to get wallet config for network: {NETWORK:?}"))?;
147166

148167
println!("{} Initializing wallet...", "⚡".bright_yellow());
@@ -241,7 +260,7 @@ async fn main() -> Result<()> {
241260
println!();
242261

243262
// Initialize wallet once at startup
244-
let mut state = WalletState::new().await?;
263+
let mut state = WalletState::new(&cli).await?;
245264

246265
// Set up signal handling for graceful shutdown
247266
let shutdown_state = state.shutdown.clone();

justfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ test-cashu *args:
1616
cli:
1717
cd examples/cli && cargo run
1818

19+
cli-cashu *args:
20+
cd examples/cli && cargo run -- --cashu --npubcash-url https://npubx.cash --mint-url {{ args }}
21+
1922
cli-logs:
2023
tail -n 50 -f examples/cli/wallet_data/bitcoin/wallet.log
2124

orange-sdk/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ name = "orange_sdk"
1616
default = ["spark"]
1717
uniffi = ["dep:uniffi", "spark", "cashu", "rand", "pin-project-lite"]
1818
spark = ["breez-sdk-spark", "uuid", "serde_json"]
19-
cashu = ["cdk", "serde_json"]
19+
cashu = ["cdk", "cdk/npubcash", "serde_json"]
2020
_test-utils = ["corepc-node", 'electrsd', "cashu", "uuid/v7", "rand"]
2121
_cashu-tests = ["_test-utils", "cdk-ldk-node", "cdk/mint", "cdk-sqlite", "cdk-axum", "axum"]
2222

orange-sdk/src/ffi/cashu.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,24 +45,34 @@ pub struct CashuConfig {
4545
pub mint_url: String,
4646
/// The currency unit to use (typically Sat)
4747
pub unit: CurrencyUnit,
48+
/// Optional npub.cash URL for lightning address support
49+
pub npubcash_url: Option<String>,
4850
}
4951

5052
#[uniffi::export]
5153
impl CashuConfig {
5254
#[uniffi::constructor]
53-
pub fn new(mint_url: String, unit: CurrencyUnit) -> Self {
54-
CashuConfig { mint_url, unit }
55+
pub fn new(mint_url: String, unit: CurrencyUnit, npubcash_url: Option<String>) -> Self {
56+
CashuConfig { mint_url, unit, npubcash_url }
5557
}
5658
}
5759

5860
impl From<CashuConfig> for OrangeCashuConfig {
5961
fn from(config: CashuConfig) -> Self {
60-
OrangeCashuConfig { mint_url: config.mint_url, unit: config.unit.into() }
62+
OrangeCashuConfig {
63+
mint_url: config.mint_url,
64+
unit: config.unit.into(),
65+
npubcash_url: config.npubcash_url,
66+
}
6167
}
6268
}
6369

6470
impl From<OrangeCashuConfig> for CashuConfig {
6571
fn from(config: OrangeCashuConfig) -> Self {
66-
CashuConfig { mint_url: config.mint_url, unit: config.unit.into() }
72+
CashuConfig {
73+
mint_url: config.mint_url,
74+
unit: config.unit.into(),
75+
npubcash_url: config.npubcash_url,
76+
}
6777
}
6878
}

orange-sdk/src/lib.rs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,27 @@ impl Wallet {
529529

530530
let tx_metadata = TxMetadataStore::new(Arc::clone(&store)).await;
531531

532+
// Cashu must init before LDK Node because CashuKvDatabase does
533+
// synchronous SQLite reads that deadlock with LDK Node's background
534+
// store writes. Other backends can init concurrently.
535+
#[cfg(feature = "cashu")]
536+
let cashu_wallet = if let ExtraConfig::Cashu(cashu) = &config.extra_config {
537+
Some(
538+
Cashu::init(
539+
&config,
540+
cashu.clone(),
541+
Arc::clone(&store),
542+
Arc::clone(&event_queue),
543+
tx_metadata.clone(),
544+
Arc::clone(&logger),
545+
Arc::clone(&runtime),
546+
)
547+
.await?,
548+
)
549+
} else {
550+
None
551+
};
552+
532553
let (trusted, ln_wallet) = tokio::join!(
533554
async {
534555
let trusted: Arc<Box<DynTrustedWalletInterface>> = match &config.extra_config {
@@ -546,18 +567,9 @@ impl Wallet {
546567
.await?,
547568
)),
548569
#[cfg(feature = "cashu")]
549-
ExtraConfig::Cashu(cashu) => Arc::new(Box::new(
550-
Cashu::init(
551-
&config,
552-
cashu.clone(),
553-
Arc::clone(&store),
554-
Arc::clone(&event_queue),
555-
tx_metadata.clone(),
556-
Arc::clone(&logger),
557-
Arc::clone(&runtime),
558-
)
559-
.await?,
560-
)),
570+
ExtraConfig::Cashu(_) => {
571+
Arc::new(Box::new(cashu_wallet.expect("initialized above")))
572+
},
561573
#[cfg(feature = "_test-utils")]
562574
ExtraConfig::Dummy(cfg) => Arc::new(Box::new(
563575
DummyTrustedWallet::new(

0 commit comments

Comments
 (0)