@@ -308,50 +308,63 @@ fn extract_payment_methods<'a>(
308308
309309impl fmt:: Display for Bip321UriBuilder {
310310 fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
311- f . write_str ( "bitcoin :") ? ;
311+ let mut uri = String :: from ( "BITCOIN :") ;
312312
313- // On-chain address goes in the path
314313 if let Some ( ref addr) = self . onchain_address {
315- f . write_str ( addr) ? ;
314+ uri . push_str ( & uppercase_qr_address ( addr) ) ;
316315 }
317316
318317 let mut query_params = form_urlencoded:: Serializer :: new ( String :: new ( ) ) ;
319318
320319 if let Some ( amount_sats) = self . amount_sats {
321320 let amount_btc = format_btc_amount_from_sats ( amount_sats) ;
322- query_params. append_pair ( "amount " , & amount_btc) ;
321+ query_params. append_pair ( "AMOUNT " , & amount_btc) ;
323322 }
324323
325324 if let Some ( ref label) = self . label {
326- query_params. append_pair ( "label " , label) ;
325+ query_params. append_pair ( "LABEL " , & label. to_ascii_uppercase ( ) ) ;
327326 }
328327
329328 if let Some ( ref message) = self . message {
330- query_params. append_pair ( "message " , message) ;
329+ query_params. append_pair ( "MESSAGE " , & message. to_ascii_uppercase ( ) ) ;
331330 }
332331
333332 if let Some ( ref bolt11) = self . bolt11_invoice {
334- query_params. append_pair ( "lightning " , bolt11) ;
333+ query_params. append_pair ( "LIGHTNING " , & bolt11. to_ascii_uppercase ( ) ) ;
335334 }
336335
337336 if let Some ( ref bolt12) = self . bolt12_offer {
338- query_params. append_pair ( "lno " , bolt12) ;
337+ query_params. append_pair ( "LNO " , & bolt12. to_ascii_uppercase ( ) ) ;
339338 }
340339
341340 if let Some ( ref creq) = self . cashu_request_str {
342- query_params. append_pair ( "creq " , creq) ;
341+ query_params. append_pair ( "CREQ " , & creq. to_ascii_uppercase ( ) ) ;
343342 }
344343
345344 let query_string = query_params. finish ( ) ;
346345 if !query_string. is_empty ( ) {
347- f . write_str ( "?" ) ? ;
348- f . write_str ( & query_string) ? ;
346+ uri . push ( '?' ) ;
347+ uri . push_str ( & query_string) ;
349348 }
350349
351- Ok ( ( ) )
350+ f . write_str ( & uri )
352351 }
353352}
354353
354+ fn uppercase_qr_address ( address : & str ) -> String {
355+ if is_qr_friendly_segwit_address ( address) {
356+ return address. to_ascii_uppercase ( ) ;
357+ }
358+
359+ address. to_string ( )
360+ }
361+
362+ fn is_qr_friendly_segwit_address ( address : & str ) -> bool {
363+ let lowercase = address. to_ascii_lowercase ( ) ;
364+
365+ lowercase. starts_with ( "bc1" ) || lowercase. starts_with ( "tb1" ) || lowercase. starts_with ( "bcrt1" )
366+ }
367+
355368/// Format satoshis to BTC string without trailing zeros, per BIP 21.
356369fn format_btc_amount_from_sats ( sats : u64 ) -> String {
357370 let whole = sats / 100_000_000 ;
@@ -377,8 +390,8 @@ mod tests {
377390 }
378391
379392 fn assert_has_creq ( uri : & str ) {
380- assert ! ( uri. starts_with( "bitcoin :?" ) || uri. starts_with( "bitcoin:bc1 " ) ) ;
381- assert ! ( uri. contains( "creq =" ) ) ;
393+ assert ! ( uri. starts_with( "BITCOIN :?" ) || uri. starts_with( "BITCOIN:BC1 " ) ) ;
394+ assert ! ( uri. contains( "CREQ =" ) ) ;
382395 }
383396
384397 fn assert_demo_cashu ( parsed : & ParsedPaymentInstruction ) {
@@ -410,16 +423,16 @@ mod tests {
410423 . with_message ( "Coffee payment" . to_string ( ) )
411424 . to_string ( ) ;
412425
413- assert ! ( uri. starts_with( & format!( "bitcoin:{addr }?" ) ) ) ;
414- assert ! ( uri. contains( "creq =" ) ) ;
415- assert ! ( uri. contains( "amount =0.001" ) ) ;
416- assert ! ( uri. contains( "message=Coffee+payment " ) ) ;
426+ assert ! ( uri. starts_with( & format!( "BITCOIN:{ }?" , addr . to_ascii_uppercase ( ) ) ) ) ;
427+ assert ! ( uri. contains( "CREQ =" ) ) ;
428+ assert ! ( uri. contains( "AMOUNT =0.001" ) ) ;
429+ assert ! ( uri. contains( "MESSAGE=COFFEE+PAYMENT " ) ) ;
417430 }
418431
419432 #[ test]
420433 fn test_bip321_uri_empty_params ( ) {
421434 let uri = Bip321UriBuilder :: new ( ) . to_string ( ) ;
422- assert_eq ! ( uri, "bitcoin :" ) ;
435+ assert_eq ! ( uri, "BITCOIN :" ) ;
423436 }
424437
425438 #[ test]
@@ -436,7 +449,7 @@ mod tests {
436449 . with_label ( "a=b&c=d" . to_string ( ) )
437450 . with_message ( "hello world" . to_string ( ) )
438451 . to_string ( ) ;
439- assert_eq ! ( uri, "bitcoin:?label=a%3Db%26c%3Dd&message=hello+world " ) ;
452+ assert_eq ! ( uri, "BITCOIN:?LABEL=A%3DB%26C%3DD&MESSAGE=HELLO+WORLD " ) ;
440453 }
441454
442455 #[ tokio:: test]
@@ -449,19 +462,19 @@ mod tests {
449462
450463 #[ tokio:: test]
451464 async fn test_parse_bip321_uri_with_cashu ( ) {
452- let uri = format ! ( "bitcoin:?creq ={TEST_CREQ}" ) ;
465+ let uri = format ! ( "BITCOIN:?CREQ ={TEST_CREQ}" ) ;
453466 let parsed = parse_payment_instruction ( & uri, bitcoin:: Network :: Bitcoin )
454467 . await
455- . expect ( "should parse bitcoin : URI with creq " ) ;
468+ . expect ( "should parse BITCOIN : URI with CREQ " ) ;
456469 assert_demo_cashu ( & parsed) ;
457470 }
458471
459472 #[ tokio:: test]
460473 async fn test_parse_bip321_uri_with_cashu_and_amount ( ) {
461- let uri = format ! ( "bitcoin:?creq ={TEST_CREQ}&amount =0.00001" ) ;
474+ let uri = format ! ( "BITCOIN:?CREQ ={TEST_CREQ}&AMOUNT =0.00001" ) ;
462475 let parsed = parse_payment_instruction ( & uri, bitcoin:: Network :: Bitcoin )
463476 . await
464- . expect ( "should parse bitcoin : URI with creq and amount " ) ;
477+ . expect ( "should parse BITCOIN : URI with CREQ and AMOUNT " ) ;
465478
466479 assert_eq ! ( parsed. cashu_requests. len( ) , 1 ) ;
467480 assert_eq ! ( parsed. amount_msats, Some ( 1_000_000 ) ) ;
@@ -521,9 +534,9 @@ mod tests {
521534 . with_onchain_address ( "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq" . to_string ( ) )
522535 . with_amount_sats ( 100_000 )
523536 . to_string ( ) ;
524- assert ! ( uri. contains( "creq =" ) ) ;
525- assert ! ( uri. contains( "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq " ) ) ;
526- assert ! ( uri. contains( "amount =0.001" ) ) ;
537+ assert ! ( uri. contains( "CREQ =" ) ) ;
538+ assert ! ( uri. contains( "BC1QAR0SRRR7XFKVY5L643LYDNW9RE59GTZZWF5MDQ " ) ) ;
539+ assert ! ( uri. contains( "AMOUNT =0.001" ) ) ;
527540 }
528541
529542 #[ tokio:: test]
@@ -544,18 +557,48 @@ mod tests {
544557 let uri = Bip321UriBuilder :: new ( )
545558 . with_bolt12_offer ( offer. to_string ( ) )
546559 . to_string ( ) ;
547- assert ! ( uri. starts_with( "bitcoin :?" ) ) ;
548- assert ! ( uri. contains( & format!( "lno={offer}" ) ) ) ;
549- assert ! ( !uri. contains( "creq =" ) ) ;
550- assert ! ( !uri. contains( "lightning =" ) ) ;
560+ assert ! ( uri. starts_with( "BITCOIN :?" ) ) ;
561+ assert ! ( uri. contains( & format!( "LNO={}" , offer . to_ascii_uppercase ( ) ) ) ) ;
562+ assert ! ( !uri. contains( "CREQ =" ) ) ;
563+ assert ! ( !uri. contains( "LIGHTNING =" ) ) ;
551564 }
552565
553566 #[ test]
554567 fn test_bip321_uri_with_label ( ) {
555568 let uri = Bip321UriBuilder :: new ( )
556569 . with_label ( "Donation" . to_string ( ) )
557570 . to_string ( ) ;
558- assert ! ( uri. contains( "label=Donation" ) ) ;
571+ assert ! ( uri. contains( "LABEL=DONATION" ) ) ;
572+ }
573+
574+ #[ test]
575+ fn test_base58_address_preserved ( ) {
576+ let addr = "175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W" ;
577+ let uri = Bip321UriBuilder :: new ( )
578+ . with_onchain_address ( addr. to_string ( ) )
579+ . to_string ( ) ;
580+
581+ assert_eq ! ( uri, format!( "BITCOIN:{addr}" ) ) ;
582+ }
583+
584+ #[ test]
585+ fn test_testnet_segwit_address_is_uppercased ( ) {
586+ let addr = "tb1qghfhmd4zh7ncpmxl3qzhmq566jk8ckq4gafnmg" ;
587+ let uri = Bip321UriBuilder :: new ( )
588+ . with_onchain_address ( addr. to_string ( ) )
589+ . to_string ( ) ;
590+
591+ assert_eq ! ( uri, format!( "BITCOIN:{}" , addr. to_ascii_uppercase( ) ) ) ;
592+ }
593+
594+ #[ test]
595+ fn test_regtest_segwit_address_is_uppercased ( ) {
596+ let addr = "bcrt1qxlvaw9k0v4m4u6sz5s4z7qjv3f4x0n8xk5m9z2" ;
597+ let uri = Bip321UriBuilder :: new ( )
598+ . with_onchain_address ( addr. to_string ( ) )
599+ . to_string ( ) ;
600+
601+ assert_eq ! ( uri, format!( "BITCOIN:{}" , addr. to_ascii_uppercase( ) ) ) ;
559602 }
560603
561604 #[ tokio:: test]
0 commit comments