@@ -1303,6 +1303,136 @@ async fn simple_bolt12_send_receive() {
13031303 assert_eq ! ( node_a_payments. first( ) . unwrap( ) . amount_msat, Some ( overpaid_amount) ) ;
13041304}
13051305
1306+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
1307+ async fn bolt12_with_blip42_contact ( ) {
1308+ let ( bitcoind, electrsd) = setup_bitcoind_and_electrsd ( ) ;
1309+ let chain_source = TestChainSource :: Esplora ( & electrsd) ;
1310+ let ( node_a, node_b) = setup_two_nodes ( & chain_source, false , true , false ) ;
1311+
1312+ let address_a = node_a. onchain_payment ( ) . new_address ( ) . unwrap ( ) ;
1313+ let premine_amount_sat = 5_000_000 ;
1314+ premine_and_distribute_funds (
1315+ & bitcoind. client ,
1316+ & electrsd. client ,
1317+ vec ! [ address_a] ,
1318+ Amount :: from_sat ( premine_amount_sat) ,
1319+ )
1320+ . await ;
1321+
1322+ node_a. sync_wallets ( ) . unwrap ( ) ;
1323+ open_channel ( & node_a, & node_b, 4_000_000 , true , & electrsd) . await ;
1324+
1325+ generate_blocks_and_wait ( & bitcoind. client , & electrsd. client , 6 ) . await ;
1326+
1327+ node_a. sync_wallets ( ) . unwrap ( ) ;
1328+ node_b. sync_wallets ( ) . unwrap ( ) ;
1329+
1330+ expect_channel_ready_event ! ( node_a, node_b. node_id( ) ) ;
1331+ expect_channel_ready_event ! ( node_b, node_a. node_id( ) ) ;
1332+
1333+ // Sleep until we broadcasted a node announcement.
1334+ while node_b. status ( ) . latest_node_announcement_broadcast_timestamp . is_none ( ) {
1335+ tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 10 ) ) . await ;
1336+ }
1337+ while node_a. status ( ) . latest_node_announcement_broadcast_timestamp . is_none ( ) {
1338+ tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 10 ) ) . await ;
1339+ }
1340+
1341+ // Sleep one more sec to make sure the node announcements propagate.
1342+ tokio:: time:: sleep ( std:: time:: Duration :: from_secs ( 1 ) ) . await ;
1343+
1344+ // Node B creates an offer to receive payment
1345+ let expected_amount_msat = 100_000_000 ;
1346+ let node_b_offer =
1347+ node_b. bolt12_payment ( ) . receive ( expected_amount_msat, "test payment" , None , Some ( 1 ) ) . unwrap ( ) ;
1348+
1349+ // Node A creates a COMPACT contact offer for BLIP-42's payer_offer field.
1350+ // Using None for intro_node creates an offer with no blinded paths (maximum compactness).
1351+ // This is suitable for embedding in invoice requests per BLIP-42 specification.
1352+ let node_a_offer = node_a. bolt12_payment ( ) . create_contact_offer ( None ) . unwrap ( ) ;
1353+
1354+ // Verify the contact offer is compact (either no paths or single-hop paths)
1355+ assert ! (
1356+ node_a_offer. paths( ) . is_empty( )
1357+ || node_a_offer. paths( ) . iter( ) . all( |p| p. blinded_hops( ) . len( ) <= 1 ) ,
1358+ "Contact offer should be compact with no paths or single-hop paths"
1359+ ) ;
1360+
1361+ // Create a contact secret (32 random bytes)
1362+ use ldk_node:: payment:: { ContactSecret , ContactSecrets } ;
1363+ let contact_secret_bytes: [ u8 ; 32 ] = std:: array:: from_fn ( |i| i as u8 ) ;
1364+ let contact_secret = ContactSecret :: new ( contact_secret_bytes) ;
1365+ let contact_secrets = ContactSecrets :: new ( contact_secret) ;
1366+
1367+ // Node A sends payment to Node B with BLIP-42 contact info
1368+ let payment_id = node_a
1369+ . bolt12_payment ( )
1370+ . send_with_contact (
1371+ & node_b_offer,
1372+ Some ( 1 ) ,
1373+ Some ( "BLIP-42 test" . to_string ( ) ) ,
1374+ None ,
1375+ Some ( contact_secrets) ,
1376+ Some ( node_a_offer. clone ( ) ) ,
1377+ )
1378+ . unwrap ( ) ;
1379+
1380+ expect_payment_successful_event ! ( node_a, Some ( payment_id) , None ) ;
1381+
1382+ // Node B should receive the payment with BLIP-42 contact information
1383+ let event = node_b. next_event_async ( ) . await ;
1384+ match event {
1385+ Event :: PaymentReceived {
1386+ amount_msat,
1387+ contact_secret : received_contact_secret,
1388+ payer_offer : received_payer_offer,
1389+ ..
1390+ } => {
1391+ println ! ( "Node B received payment with BLIP-42 contact info" ) ;
1392+ assert_eq ! ( amount_msat, expected_amount_msat) ;
1393+
1394+ // Verify contact_secret is present and matches
1395+ assert ! ( received_contact_secret. is_some( ) , "Expected contact_secret to be Some" ) ;
1396+ assert_eq ! (
1397+ received_contact_secret. unwrap( ) ,
1398+ contact_secret_bytes. to_vec( ) ,
1399+ "Contact secret mismatch"
1400+ ) ;
1401+
1402+ // Verify payer_offer is present
1403+ assert ! ( received_payer_offer. is_some( ) , "Expected payer_offer to be Some" ) ;
1404+ let payer_offer_str = received_payer_offer. unwrap ( ) ;
1405+
1406+ // Parse the payer_offer and verify it matches node_a's offer
1407+ let parsed_offer: lightning:: offers:: offer:: Offer =
1408+ payer_offer_str. parse ( ) . expect ( "Failed to parse payer_offer" ) ;
1409+ assert_eq ! (
1410+ parsed_offer. id( ) ,
1411+ node_a_offer. id( ) ,
1412+ "Parsed offer ID should match node A's offer"
1413+ ) ;
1414+
1415+ node_b. event_handled ( ) . unwrap ( ) ;
1416+ } ,
1417+ ref e => {
1418+ panic ! ( "Expected PaymentReceived event, got: {:?}" , e) ;
1419+ } ,
1420+ }
1421+
1422+ // Verify payment records
1423+ let node_a_payments =
1424+ node_a. list_payments_with_filter ( |p| matches ! ( p. kind, PaymentKind :: Bolt12Offer { .. } ) ) ;
1425+ assert_eq ! ( node_a_payments. len( ) , 1 ) ;
1426+ assert_eq ! ( node_a_payments. first( ) . unwrap( ) . amount_msat, Some ( expected_amount_msat) ) ;
1427+ assert_eq ! ( node_a_payments. first( ) . unwrap( ) . status, PaymentStatus :: Succeeded ) ;
1428+
1429+ let node_b_payments =
1430+ node_b. list_payments_with_filter ( |p| matches ! ( p. kind, PaymentKind :: Bolt12Offer { .. } ) ) ;
1431+ assert_eq ! ( node_b_payments. len( ) , 1 ) ;
1432+ assert_eq ! ( node_b_payments. first( ) . unwrap( ) . amount_msat, Some ( expected_amount_msat) ) ;
1433+ assert_eq ! ( node_b_payments. first( ) . unwrap( ) . status, PaymentStatus :: Succeeded ) ;
1434+ }
1435+
13061436#[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
13071437async fn async_payment ( ) {
13081438 let ( bitcoind, electrsd) = setup_bitcoind_and_electrsd ( ) ;
0 commit comments