diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index c2f0166c8..9f0e2d7ed 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -213,6 +213,10 @@ interface SpontaneousPayment { PaymentId send_with_custom_tlvs(u64 amount_msat, PublicKey node_id, SendingParameters? sending_parameters, sequence custom_tlvs); [Throws=NodeError] void send_probes(u64 amount_msat, PublicKey node_id); + [Throws=NodeError] + PaymentId send_with_preimage(u64 amount_msat, PublicKey node_id, SendingParameters? sending_parameters, PaymentPreimage preimage); + [Throws=NodeError] + PaymentId send_with_preimage_and_custom_tlvs(u64 amount_msat, PublicKey node_id, SendingParameters? sending_parameters, sequence custom_tlvs, PaymentPreimage preimage); }; interface OnchainPayment { diff --git a/src/payment/spontaneous.rs b/src/payment/spontaneous.rs index 1508b6cd8..4472a4f97 100644 --- a/src/payment/spontaneous.rs +++ b/src/payment/spontaneous.rs @@ -57,7 +57,7 @@ impl SpontaneousPayment { pub fn send( &self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option, ) -> Result { - self.send_inner(amount_msat, node_id, sending_parameters, None) + self.send_inner(amount_msat, node_id, sending_parameters, None, None) } /// Send a spontaneous payment including a list of custom TLVs. @@ -65,19 +65,36 @@ impl SpontaneousPayment { &self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option, custom_tlvs: Vec, ) -> Result { - self.send_inner(amount_msat, node_id, sending_parameters, Some(custom_tlvs)) + self.send_inner(amount_msat, node_id, sending_parameters, Some(custom_tlvs), None) + } + + /// Send a spontaneous payment with custom preimage + pub fn send_with_preimage( + &self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option, + preimage: PaymentPreimage, + ) -> Result { + self.send_inner(amount_msat, node_id, sending_parameters, None, Some(preimage)) + } + + /// Send a spontaneous payment with custom preimage including a list of custom TLVs. + pub fn send_with_preimage_and_custom_tlvs( + &self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option, + custom_tlvs: Vec, preimage: PaymentPreimage, + ) -> Result { + self.send_inner(amount_msat, node_id, sending_parameters, Some(custom_tlvs), Some(preimage)) } fn send_inner( &self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option, - custom_tlvs: Option>, + custom_tlvs: Option>, preimage: Option, ) -> Result { let rt_lock = self.runtime.read().unwrap(); if rt_lock.is_none() { return Err(Error::NotRunning); } - let payment_preimage = PaymentPreimage(self.keys_manager.get_secure_random_bytes()); + let payment_preimage = preimage + .unwrap_or_else(|| PaymentPreimage(self.keys_manager.get_secure_random_bytes())); let payment_hash = PaymentHash::from(payment_preimage); let payment_id = PaymentId(payment_hash.0); diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index db48eca23..ca5bd771a 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -19,8 +19,8 @@ use common::{ use ldk_node::config::EsploraSyncConfig; use ldk_node::liquidity::LSPS2ServiceConfig; use ldk_node::payment::{ - ConfirmationStatus, PaymentDirection, PaymentKind, PaymentStatus, QrPaymentResult, - SendingParameters, + ConfirmationStatus, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, + QrPaymentResult, SendingParameters, }; use ldk_node::{Builder, Event, NodeError}; @@ -29,8 +29,10 @@ use lightning::routing::gossip::{NodeAlias, NodeId}; use lightning::util::persist::KVStore; use lightning_invoice::{Bolt11InvoiceDescription, Description}; +use lightning_types::payment::PaymentPreimage; use bitcoin::address::NetworkUnchecked; +use bitcoin::hashes::sha256::Hash as Sha256Hash; use bitcoin::hashes::Hash; use bitcoin::Address; use bitcoin::Amount; @@ -1381,3 +1383,69 @@ fn facade_logging() { validate_log_entry(entry); } } + +#[test] +fn spontaneous_send_with_custom_preimage() { + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let chain_source = TestChainSource::Esplora(&electrsd); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + + let address_a = node_a.onchain_payment().new_address().unwrap(); + let premine_sat = 1_000_000; + premine_and_distribute_funds( + &bitcoind.client, + &electrsd.client, + vec![address_a], + Amount::from_sat(premine_sat), + ); + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + open_channel(&node_a, &node_b, 500_000, true, &electrsd); + generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6); + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + expect_channel_ready_event!(node_a, node_b.node_id()); + expect_channel_ready_event!(node_b, node_a.node_id()); + + let seed = b"test_payment_preimage"; + let bytes: Sha256Hash = Sha256Hash::hash(seed); + let custom_bytes = bytes.to_byte_array(); + let custom_preimage = PaymentPreimage(custom_bytes); + + let amount_msat = 100_000; + let payment_id = node_a + .spontaneous_payment() + .send_with_preimage(amount_msat, node_b.node_id(), None, custom_preimage.clone()) + .unwrap(); + + // check payment status and verify stored preimage + expect_payment_successful_event!(node_a, Some(payment_id), None); + let details: PaymentDetails = + node_a.list_payments_with_filter(|p| p.id == payment_id).first().unwrap().clone(); + assert_eq!(details.status, PaymentStatus::Succeeded); + if let PaymentKind::Spontaneous { preimage: Some(pi), .. } = details.kind { + assert_eq!(pi.0, custom_bytes); + } else { + panic!("Expected a spontaneous PaymentKind with a preimage"); + } + + // Verify receiver side (node_b) + expect_payment_received_event!(node_b, amount_msat); + let receiver_payments: Vec = node_b.list_payments_with_filter(|p| { + p.direction == PaymentDirection::Inbound + && matches!(p.kind, PaymentKind::Spontaneous { .. }) + }); + + assert_eq!(receiver_payments.len(), 1); + let receiver_details = &receiver_payments[0]; + assert_eq!(receiver_details.status, PaymentStatus::Succeeded); + assert_eq!(receiver_details.amount_msat, Some(amount_msat)); + assert_eq!(receiver_details.direction, PaymentDirection::Inbound); + + // Verify receiver also has the same preimage + if let PaymentKind::Spontaneous { preimage: Some(pi), .. } = receiver_details.kind { + assert_eq!(pi.0, custom_bytes); + } else { + panic!("Expected receiver to have spontaneous PaymentKind with preimage"); + } +}