Skip to content

Commit c82484e

Browse files
authored
Upgrade to ldk-node 0.7 (#26)
1 parent 47b2045 commit c82484e

21 files changed

Lines changed: 1893 additions & 813 deletions

File tree

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ codegen-units = 1 # Reduce number of codegen units to increase optimizations.
1616
panic = 'abort' # Abort on panic
1717

1818
[workspace.dependencies]
19-
bitcoin-payment-instructions = { git = "https://github.com/benthecarman/bitcoin-payment-instructions.git", branch = "orange-fork", features = ["http"] }
20-
lightning = { git = "https://github.com/tnull/rust-lightning", branch = "2025-08-bump-electrum-client-0.1" }
21-
lightning-invoice = { git = "https://github.com/tnull/rust-lightning", branch = "2025-08-bump-electrum-client-0.1" }
19+
bitcoin-payment-instructions = { version = "0.6.0" }
20+
lightning = { version = "0.2.0" }
21+
lightning-invoice = { version = "0.34.0" }
2222

2323
[profile.release]
2424
panic = "abort"

examples/cli/src/main.rs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ use std::path::PathBuf;
1616
use std::str::FromStr;
1717
use std::sync::Arc;
1818
use std::sync::atomic::{AtomicBool, Ordering};
19-
use tokio::runtime::Runtime;
2019
use tokio::signal;
2120

2221
const NETWORK: Network = Network::Bitcoin; // Supports Bitcoin and Regtest
@@ -58,7 +57,6 @@ enum Commands {
5857

5958
struct WalletState {
6059
wallet: Wallet,
61-
_runtime: Arc<Runtime>, // Keep runtime alive
6260
shutdown: Arc<AtomicBool>,
6361
}
6462

@@ -128,20 +126,20 @@ fn get_config(network: Network) -> Result<WalletConfig> {
128126
}
129127

130128
impl WalletState {
131-
async fn new(runtime: Arc<Runtime>) -> Result<Self> {
129+
async fn new() -> Result<Self> {
132130
let shutdown = Arc::new(AtomicBool::new(false));
133131
let config = get_config(NETWORK)
134132
.with_context(|| format!("Failed to get wallet config for network: {NETWORK:?}"))?;
135133

136134
println!("{} Initializing wallet...", "⚡".bright_yellow());
137135

138-
match Wallet::new_with_runtime(runtime.clone(), config).await {
136+
match Wallet::new(config).await {
139137
Ok(wallet) => {
140138
println!("{} Wallet initialized successfully!", "✅".bright_green());
141139
println!("Network: {}", NETWORK.to_string().bright_cyan());
142140

143141
let w = wallet.clone();
144-
runtime.spawn(async move {
142+
tokio::spawn(async move {
145143
let event = w.next_event_async().await;
146144
match event {
147145
Event::PaymentSuccessful { payment_id, .. } => {
@@ -193,12 +191,19 @@ impl WalletState {
193191
fee_msat
194192
);
195193
},
194+
Event::SplicePending { new_funding_txo, .. } => {
195+
println!(
196+
"{} Splice pending: {}",
197+
"🔄".bright_yellow(),
198+
new_funding_txo
199+
);
200+
},
196201
}
197202

198203
w.event_handled().unwrap();
199204
});
200205

201-
Ok(WalletState { wallet, _runtime: runtime, shutdown })
206+
Ok(WalletState { wallet, shutdown })
202207
},
203208
Err(e) => Err(anyhow::anyhow!("Failed to initialize wallet: {:?}", e)),
204209
}
@@ -213,23 +218,21 @@ impl WalletState {
213218
}
214219
}
215220

216-
fn main() -> Result<()> {
221+
#[tokio::main(flavor = "multi_thread")]
222+
async fn main() -> Result<()> {
217223
let cli = Cli::parse();
218224

219225
println!("{}", "🟠 Orange CLI Wallet".bright_yellow().bold());
220226
println!("{}", "Type 'help' for available commands or 'exit' to quit".dimmed());
221227
println!();
222228

223-
// Create runtime outside async context to avoid drop issues
224-
let runtime = Arc::new(Runtime::new().context("Failed to create tokio runtime")?);
225-
226229
// Initialize wallet once at startup
227-
let mut state = runtime.block_on(WalletState::new(runtime.clone()))?;
230+
let mut state = WalletState::new().await?;
228231

229232
// Set up signal handling for graceful shutdown
230233
let shutdown_state = state.shutdown.clone();
231234
let shutdown_wallet = state.wallet.clone();
232-
runtime.spawn(async move {
235+
tokio::task::spawn(async move {
233236
if let Ok(()) = signal::ctrl_c().await {
234237
println!("\n{} Shutdown signal received, stopping wallet...", "⏹️".bright_yellow());
235238
shutdown_state.store(true, Ordering::Relaxed);
@@ -241,12 +244,12 @@ fn main() -> Result<()> {
241244

242245
// If a command was provided via command line, execute it and start interactive mode
243246
if let Some(command) = cli.command {
244-
runtime.block_on(execute_command(command, &mut state))?;
247+
execute_command(command, &mut state).await?;
245248
println!();
246249
}
247250

248251
// Start interactive mode
249-
runtime.block_on(start_interactive_mode(state))
252+
start_interactive_mode(state).await
250253
}
251254

252255
async fn start_interactive_mode(mut state: WalletState) -> Result<()> {

graduated-rebalancer/src/lib.rs

Lines changed: 77 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ pub trait LightningWallet: Send + Sync {
108108
&self, payment_hash: [u8; 32],
109109
) -> Pin<Box<dyn Future<Output = Option<ReceivedLightningPayment>> + Send + '_>>;
110110

111+
/// Check if we already have a channel with the LSP
112+
fn has_channel_with_lsp(&self) -> bool;
113+
111114
/// Open a channel with the LSP using on-chain funds
112115
fn open_channel_with_lsp(
113116
&self, amt: Amount,
@@ -117,6 +120,16 @@ pub trait LightningWallet: Send + Sync {
117120
fn await_channel_pending(
118121
&self, channel_id: u128,
119122
) -> Pin<Box<dyn Future<Output = OutPoint> + Send + '_>>;
123+
124+
/// Splice funds from on-chain to an existing channel with the LSP
125+
fn splice_to_lsp_channel(
126+
&self, amt: Amount,
127+
) -> Pin<Box<dyn Future<Output = Result<u128, Self::Error>> + Send + '_>>;
128+
129+
/// Wait for a splice pending notification, returns the splice outpoint
130+
fn await_splice_pending(
131+
&self, channel_id: u128,
132+
) -> Pin<Box<dyn Future<Output = OutPoint> + Send + '_>>;
120133
}
121134

122135
/// Represents a payment from the lightning wallet
@@ -176,15 +189,19 @@ pub enum RebalancerEvent {
176189
/// Trait for handling rebalancer events
177190
pub trait EventHandler: Send + Sync {
178191
/// Handle a rebalancer event
179-
fn handle_event(&self, event: RebalancerEvent);
192+
fn handle_event(&self, event: RebalancerEvent)
193+
-> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
180194
}
181195

182196
/// A no-op event handler that discards all events
183197
#[derive(Debug, Copy, Clone, Default)]
184198
pub struct IgnoringEventHandler;
185199

186200
impl EventHandler for IgnoringEventHandler {
187-
fn handle_event(&self, _event: RebalancerEvent) {
201+
fn handle_event(
202+
&self, _event: RebalancerEvent,
203+
) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
204+
Box::pin(async move {})
188205
// Do nothing
189206
}
190207
}
@@ -260,11 +277,13 @@ where
260277
rebalance_id.as_hex()
261278
);
262279

263-
self.event_handler.handle_event(RebalancerEvent::RebalanceInitiated {
264-
trigger_id: params.id,
265-
trusted_rebalance_payment_id: rebalance_id,
266-
amount_msat: transfer_amt.milli_sats(),
267-
});
280+
self.event_handler
281+
.handle_event(RebalancerEvent::RebalanceInitiated {
282+
trigger_id: params.id,
283+
trusted_rebalance_payment_id: rebalance_id,
284+
amount_msat: transfer_amt.milli_sats(),
285+
})
286+
.await;
268287

269288
let ln_payment = match self
270289
.ln_wallet
@@ -297,14 +316,16 @@ where
297316
ln_payment.id.as_hex(),
298317
);
299318

300-
self.event_handler.handle_event(RebalancerEvent::RebalanceSuccessful {
301-
trigger_id: params.id,
302-
trusted_rebalance_payment_id: rebalance_id,
303-
ln_rebalance_payment_id: ln_payment.id,
304-
amount_msat: transfer_amt.milli_sats(),
305-
fee_msat: ln_payment.fee_paid_msat.unwrap_or_default()
306-
+ trusted_payment.fee_paid_msat.unwrap_or_default(),
307-
});
319+
self.event_handler
320+
.handle_event(RebalancerEvent::RebalanceSuccessful {
321+
trigger_id: params.id,
322+
trusted_rebalance_payment_id: rebalance_id,
323+
ln_rebalance_payment_id: ln_payment.id,
324+
amount_msat: transfer_amt.milli_sats(),
325+
fee_msat: ln_payment.fee_paid_msat.unwrap_or_default()
326+
+ trusted_payment.fee_paid_msat.unwrap_or_default(),
327+
})
328+
.await;
308329
},
309330
Err(e) => {
310331
log_info!(self.logger, "Rebalance trusted transaction failed with {e:?}",);
@@ -313,34 +334,55 @@ where
313334
}
314335
}
315336

316-
/// Perform on-chain to lightning rebalance by opening a channel
337+
/// Perform on-chain to lightning rebalance by opening a channel or splicing into an existing one
317338
async fn do_onchain_rebalance(&self, params: TriggerParams) {
318-
// This should open a channel with the LSP using available on-chain funds
319-
320339
let _ = self.balance_mutex.lock().await;
321340

322-
log_info!(self.logger, "Opening channel with LSP with on-chain funds");
341+
let (channel_outpoint, user_channel_id) = if self.ln_wallet.has_channel_with_lsp() {
342+
log_info!(self.logger, "Splicing into channel with LSP with on-chain funds");
323343

324-
// todo for now we can only open a channel, eventually move to splicing
325-
let user_chan_id = match self.ln_wallet.open_channel_with_lsp(params.amount).await {
326-
Ok(chan_id) => chan_id,
327-
Err(e) => {
328-
log_error!(self.logger, "Failed to open channel with LSP: {e:?}");
329-
return;
330-
},
331-
};
344+
let user_chan_id = match self.ln_wallet.splice_to_lsp_channel(params.amount).await {
345+
Ok(chan_id) => chan_id,
346+
Err(e) => {
347+
log_error!(self.logger, "Failed to open channel with LSP: {e:?}");
348+
return;
349+
},
350+
};
351+
352+
log_info!(self.logger, "Initiated splice opened with LSP");
353+
354+
let channel_outpoint = self.ln_wallet.await_splice_pending(user_chan_id).await;
355+
356+
log_info!(self.logger, "Splice initiated at: {channel_outpoint}");
357+
358+
(channel_outpoint, user_chan_id)
359+
} else {
360+
log_info!(self.logger, "Opening channel with LSP with on-chain funds");
332361

333-
log_info!(self.logger, "Initiated channel opened with LSP");
362+
let user_chan_id = match self.ln_wallet.open_channel_with_lsp(params.amount).await {
363+
Ok(chan_id) => chan_id,
364+
Err(e) => {
365+
log_error!(self.logger, "Failed to open channel with LSP: {e:?}");
366+
return;
367+
},
368+
};
369+
370+
log_info!(self.logger, "Initiated channel opened with LSP");
334371

335-
let channel_outpoint = self.ln_wallet.await_channel_pending(user_chan_id).await;
372+
let channel_outpoint = self.ln_wallet.await_channel_pending(user_chan_id).await;
336373

337-
log_info!(self.logger, "Channel open succeeded at: {channel_outpoint}",);
374+
log_info!(self.logger, "Channel open succeeded at: {channel_outpoint}");
375+
376+
(channel_outpoint, user_chan_id)
377+
};
338378

339-
self.event_handler.handle_event(RebalancerEvent::OnChainRebalanceInitiated {
340-
trigger_id: params.id,
341-
user_channel_id: user_chan_id,
342-
channel_outpoint,
343-
});
379+
self.event_handler
380+
.handle_event(RebalancerEvent::OnChainRebalanceInitiated {
381+
trigger_id: params.id,
382+
user_channel_id,
383+
channel_outpoint,
384+
})
385+
.await;
344386
}
345387

346388
/// Stops the rebalancer, waits for any active rebalances to complete

justfile

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@ default:
22
@just --list
33

44
test *args:
5-
cargo test {{ args }} --features _test-utils -p orange-sdk
5+
#!/usr/bin/env bash
6+
THREADS=$(($(nproc) / 2))
7+
if [ $THREADS -lt 1 ]; then THREADS=1; fi
8+
cargo test {{ args }} --features _test-utils -p orange-sdk -- --test-threads=$THREADS
69
710
test-cashu *args:
8-
cargo test {{ args }} --features _cashu-tests -p orange-sdk
11+
#!/usr/bin/env bash
12+
THREADS=$(($(nproc) / 2))
13+
if [ $THREADS -lt 1 ]; then THREADS=1; fi
14+
cargo test {{ args }} --features _cashu-tests -p orange-sdk -- --test-threads=$THREADS
915
1016
cli:
1117
cd examples/cli && cargo run

orange-sdk/Cargo.toml

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,33 @@ default = ["spark"]
1717
uniffi = ["dep:uniffi", "spark", "cashu"]
1818
spark = ["breez-sdk-spark", "uuid", "serde_json"]
1919
cashu = ["cdk", "serde_json"]
20-
_test-utils = ["corepc-node", "cashu", "uuid/v7", "rand"]
20+
_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

2323
[dependencies]
2424
graduated-rebalancer = { path = "../graduated-rebalancer", version = "0.1.0" }
2525

26-
ldk-node = { git = "https://github.com/benthecarman/ldk-node.git", branch = "esplora-auth" }
27-
bitcoin-payment-instructions = { workspace = true }
26+
ldk-node = { version = "0.7.0" }
27+
lightning-macros = "0.2.0"
28+
bitcoin-payment-instructions = { workspace = true, features = ["http"] }
2829
chrono = { version = "0.4", default-features = false }
2930
rand = { version = "0.8.5", optional = true }
30-
reqwest = { version = "0.12.23", default-features = false, features = ["rustls-tls"] }
3131
breez-sdk-spark = { git = "https://github.com/breez/spark-sdk.git", rev = "1f2e9995230cd582d6b4aa7d06d76b99defb635e", default-features = false, features = ["rustls-tls"], optional = true }
3232
tokio = { version = "1.0", default-features = false, features = ["rt-multi-thread", "sync"] }
3333
uuid = { version = "1.0", default-features = false, optional = true }
34-
cdk = { git = "https://github.com/benthecarman/cdk.git", rev = "39c1206a4a1dda2adc1f3e23628136ef645f6c6b", default-features = false, features = ["wallet"], optional = true }
34+
cdk = { version = "0.14.2", default-features = false, features = ["wallet"], optional = true }
3535
serde_json = { version = "1.0", optional = true }
3636
async-trait = "0.1"
3737
log = "0.4.28"
3838

39-
corepc-node = { version = "0.8.0", features = ["29_0", "download"], optional = true }
40-
cdk-ldk-node = { git = "https://github.com/benthecarman/cdk.git", rev = "39c1206a4a1dda2adc1f3e23628136ef645f6c6b", optional = true }
41-
cdk-sqlite = { git = "https://github.com/benthecarman/cdk.git", rev = "39c1206a4a1dda2adc1f3e23628136ef645f6c6b", optional = true }
42-
cdk-axum = { git = "https://github.com/benthecarman/cdk.git", rev = "39c1206a4a1dda2adc1f3e23628136ef645f6c6b", optional = true }
39+
corepc-node = { version = "0.10.1", features = ["29_0", "download"], optional = true }
40+
electrsd = { version = "0.36.1", default-features = false, features = ["esplora_a33e97e1", "corepc-node_29_0"], optional = true }
41+
cdk-ldk-node = { version = "0.14.2", optional = true }
42+
cdk-sqlite = { version = "0.14.2", optional = true }
43+
cdk-axum = { version = "0.14.2", optional = true }
4344
axum = { version = "0.8.1", optional = true }
4445

4546
uniffi = { version = "0.29", features = ["cli", "tokio"], optional = true }
47+
48+
[dev-dependencies]
49+
test-log = "0.2.18"

0 commit comments

Comments
 (0)