@@ -209,9 +209,8 @@ impl MetadataStrategy for DerivedMetadata {}
209
209
macro_rules! offer_explicit_metadata_builder_methods { (
210
210
$self: ident, $self_type: ty, $return_type: ty, $return_value: expr
211
211
) => {
212
- /// Creates a new builder for an offer setting an empty [`Offer::description`] and using the
213
- /// [`Offer::signing_pubkey`] for signing invoices. The associated secret key must be remembered
214
- /// while the offer is valid.
212
+ /// Creates a new builder for an offer using the [`Offer::signing_pubkey`] for signing invoices.
213
+ /// The associated secret key must be remembered while the offer is valid.
215
214
///
216
215
/// Use a different pubkey per offer to avoid correlating offers.
217
216
///
@@ -225,7 +224,7 @@ macro_rules! offer_explicit_metadata_builder_methods { (
225
224
pub fn new( signing_pubkey: PublicKey ) -> Self {
226
225
Self {
227
226
offer: OfferContents {
228
- chains: None , metadata: None , amount: None , description: String :: new ( ) ,
227
+ chains: None , metadata: None , amount: None , description: None ,
229
228
features: OfferFeatures :: empty( ) , absolute_expiry: None , issuer: None , paths: None ,
230
229
supported_quantity: Quantity :: One , signing_pubkey: Some ( signing_pubkey) ,
231
230
} ,
@@ -264,7 +263,7 @@ macro_rules! offer_derived_metadata_builder_methods { ($secp_context: ty) => {
264
263
let metadata = Metadata :: DerivedSigningPubkey ( derivation_material) ;
265
264
Self {
266
265
offer: OfferContents {
267
- chains: None , metadata: Some ( metadata) , amount: None , description: String :: new ( ) ,
266
+ chains: None , metadata: Some ( metadata) , amount: None , description: None ,
268
267
features: OfferFeatures :: empty( ) , absolute_expiry: None , issuer: None , paths: None ,
269
268
supported_quantity: Quantity :: One , signing_pubkey: Some ( node_id) ,
270
269
} ,
@@ -330,7 +329,7 @@ macro_rules! offer_builder_methods { (
330
329
///
331
330
/// Successive calls to this method will override the previous setting.
332
331
pub fn description( $( $self_mut) * $self: $self_type, description: String ) -> $return_type {
333
- $self. offer. description = description;
332
+ $self. offer. description = Some ( description) ;
334
333
$return_value
335
334
}
336
335
@@ -373,6 +372,10 @@ macro_rules! offer_builder_methods { (
373
372
None => { } ,
374
373
}
375
374
375
+ if $self. offer. amount. is_some( ) && $self. offer. description. is_none( ) {
376
+ $self. offer. description = Some ( String :: new( ) ) ;
377
+ }
378
+
376
379
if let Some ( chains) = & $self. offer. chains {
377
380
if chains. len( ) == 1 && chains[ 0 ] == $self. offer. implied_chain( ) {
378
381
$self. offer. chains = None ;
@@ -551,7 +554,7 @@ pub(super) struct OfferContents {
551
554
chains : Option < Vec < ChainHash > > ,
552
555
metadata : Option < Metadata > ,
553
556
amount : Option < Amount > ,
554
- description : String ,
557
+ description : Option < String > ,
555
558
features : OfferFeatures ,
556
559
absolute_expiry : Option < Duration > ,
557
560
issuer : Option < String > ,
@@ -585,7 +588,7 @@ macro_rules! offer_accessors { ($self: ident, $contents: expr) => {
585
588
586
589
/// A complete description of the purpose of the payment. Intended to be displayed to the user
587
590
/// but with the caveat that it has not been verified in any way.
588
- pub fn description( & $self) -> $crate:: util:: string:: PrintableString {
591
+ pub fn description( & $self) -> Option < $crate:: util:: string:: PrintableString > {
589
592
$contents. description( )
590
593
}
591
594
@@ -809,8 +812,8 @@ impl OfferContents {
809
812
self . amount . as_ref ( )
810
813
}
811
814
812
- pub fn description ( & self ) -> PrintableString {
813
- PrintableString ( & self . description )
815
+ pub fn description ( & self ) -> Option < PrintableString > {
816
+ self . description . as_ref ( ) . map ( |description| PrintableString ( description ) )
814
817
}
815
818
816
819
pub fn features ( & self ) -> & OfferFeatures {
@@ -954,7 +957,7 @@ impl OfferContents {
954
957
metadata : self . metadata ( ) ,
955
958
currency,
956
959
amount,
957
- description : Some ( & self . description ) ,
960
+ description : self . description . as_ref ( ) ,
958
961
features,
959
962
absolute_expiry : self . absolute_expiry . map ( |duration| duration. as_secs ( ) ) ,
960
963
paths : self . paths . as_ref ( ) ,
@@ -1092,10 +1095,9 @@ impl TryFrom<OfferTlvStream> for OfferContents {
1092
1095
( Some ( iso4217_code) , Some ( amount) ) => Some ( Amount :: Currency { iso4217_code, amount } ) ,
1093
1096
} ;
1094
1097
1095
- let description = match description {
1096
- None => return Err ( Bolt12SemanticError :: MissingDescription ) ,
1097
- Some ( description) => description,
1098
- } ;
1098
+ if amount. is_some ( ) && description. is_none ( ) {
1099
+ return Err ( Bolt12SemanticError :: MissingDescription ) ;
1100
+ }
1099
1101
1100
1102
let features = features. unwrap_or_else ( OfferFeatures :: empty) ;
1101
1103
@@ -1166,7 +1168,7 @@ mod tests {
1166
1168
assert ! ( offer. supports_chain( ChainHash :: using_genesis_block( Network :: Bitcoin ) ) ) ;
1167
1169
assert_eq ! ( offer. metadata( ) , None ) ;
1168
1170
assert_eq ! ( offer. amount( ) , None ) ;
1169
- assert_eq ! ( offer. description( ) , PrintableString ( "" ) ) ;
1171
+ assert_eq ! ( offer. description( ) , None ) ;
1170
1172
assert_eq ! ( offer. offer_features( ) , & OfferFeatures :: empty( ) ) ;
1171
1173
assert_eq ! ( offer. absolute_expiry( ) , None ) ;
1172
1174
#[ cfg( feature = "std" ) ]
@@ -1183,7 +1185,7 @@ mod tests {
1183
1185
metadata: None ,
1184
1186
currency: None ,
1185
1187
amount: None ,
1186
- description: Some ( & String :: from ( "" ) ) ,
1188
+ description: None ,
1187
1189
features: None ,
1188
1190
absolute_expiry: None ,
1189
1191
paths: None ,
@@ -1421,16 +1423,23 @@ mod tests {
1421
1423
. description ( "foo" . into ( ) )
1422
1424
. build ( )
1423
1425
. unwrap ( ) ;
1424
- assert_eq ! ( offer. description( ) , PrintableString ( "foo" ) ) ;
1426
+ assert_eq ! ( offer. description( ) , Some ( PrintableString ( "foo" ) ) ) ;
1425
1427
assert_eq ! ( offer. as_tlv_stream( ) . description, Some ( & String :: from( "foo" ) ) ) ;
1426
1428
1427
1429
let offer = OfferBuilder :: new ( pubkey ( 42 ) )
1428
1430
. description ( "foo" . into ( ) )
1429
1431
. description ( "bar" . into ( ) )
1430
1432
. build ( )
1431
1433
. unwrap ( ) ;
1432
- assert_eq ! ( offer. description( ) , PrintableString ( "bar" ) ) ;
1434
+ assert_eq ! ( offer. description( ) , Some ( PrintableString ( "bar" ) ) ) ;
1433
1435
assert_eq ! ( offer. as_tlv_stream( ) . description, Some ( & String :: from( "bar" ) ) ) ;
1436
+
1437
+ let offer = OfferBuilder :: new ( pubkey ( 42 ) )
1438
+ . amount_msats ( 1000 )
1439
+ . build ( )
1440
+ . unwrap ( ) ;
1441
+ assert_eq ! ( offer. description( ) , Some ( PrintableString ( "" ) ) ) ;
1442
+ assert_eq ! ( offer. as_tlv_stream( ) . description, Some ( & String :: from( "" ) ) ) ;
1434
1443
}
1435
1444
1436
1445
#[ test]
@@ -1655,6 +1664,14 @@ mod tests {
1655
1664
panic ! ( "error parsing offer: {:?}" , e) ;
1656
1665
}
1657
1666
1667
+ let offer = OfferBuilder :: new ( pubkey ( 42 ) )
1668
+ . description ( "foo" . to_string ( ) )
1669
+ . amount_msats ( 1000 )
1670
+ . build ( ) . unwrap ( ) ;
1671
+ if let Err ( e) = offer. to_string ( ) . parse :: < Offer > ( ) {
1672
+ panic ! ( "error parsing offer: {:?}" , e) ;
1673
+ }
1674
+
1658
1675
let mut tlv_stream = offer. as_tlv_stream ( ) ;
1659
1676
tlv_stream. description = None ;
1660
1677
@@ -1875,7 +1892,7 @@ mod bolt12_tests {
1875
1892
// Malformed: empty
1876
1893
assert_eq ! (
1877
1894
"lno1" . parse:: <Offer >( ) ,
1878
- Err ( Bolt12ParseError :: InvalidSemantics ( Bolt12SemanticError :: MissingDescription ) ) ,
1895
+ Err ( Bolt12ParseError :: InvalidSemantics ( Bolt12SemanticError :: MissingSigningPubkey ) ) ,
1879
1896
) ;
1880
1897
1881
1898
// Malformed: truncated at type
@@ -2000,7 +2017,8 @@ mod bolt12_tests {
2000
2017
2001
2018
// Missing offer_description
2002
2019
assert_eq ! (
2003
- "lno1zcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese" . parse:: <Offer >( ) ,
2020
+ // TODO: Match the spec once it is updated.
2021
+ "lno1pqpq86qkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg" . parse:: <Offer >( ) ,
2004
2022
Err ( Bolt12ParseError :: InvalidSemantics ( Bolt12SemanticError :: MissingDescription ) ) ,
2005
2023
) ;
2006
2024
0 commit comments