Expand invoice module docs and include an example
[rust-lightning] / lightning / src / offers / offer.rs
index 680f4094162ff49021bd47f1a705ff6f154cb375..d92d0d8bb4ca9ec3812fc6ad3dd37b5b7b948f9e 100644 (file)
@@ -106,7 +106,7 @@ impl OfferBuilder {
                let offer = OfferContents {
                        chains: None, metadata: None, amount: None, description,
                        features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
-                       supported_quantity: Quantity::one(), signing_pubkey: Some(signing_pubkey),
+                       supported_quantity: Quantity::one(), signing_pubkey,
                };
                OfferBuilder { offer }
        }
@@ -232,7 +232,7 @@ impl OfferBuilder {
 /// An `Offer` is a potentially long-lived proposal for payment of a good or service.
 ///
 /// An offer is a precursor to an [`InvoiceRequest`]. A merchant publishes an offer from which a
-/// customer may request an `Invoice` for a specific quantity and using an amount sufficient to
+/// customer may request an [`Invoice`] for a specific quantity and using an amount sufficient to
 /// cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
 ///
 /// Offers may be denominated in currency other than bitcoin but are ultimately paid using the
@@ -241,6 +241,7 @@ impl OfferBuilder {
 /// Through the use of [`BlindedPath`]s, offers provide recipient privacy.
 ///
 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+/// [`Invoice`]: crate::offers::invoice::Invoice
 #[derive(Clone, Debug)]
 pub struct Offer {
        // The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
@@ -249,9 +250,10 @@ pub struct Offer {
        pub(super) contents: OfferContents,
 }
 
-/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or an `Invoice`.
+/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or an [`Invoice`].
 ///
 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+/// [`Invoice`]: crate::offers::invoice::Invoice
 #[derive(Clone, Debug)]
 pub(super) struct OfferContents {
        chains: Option<Vec<ChainHash>>,
@@ -263,7 +265,7 @@ pub(super) struct OfferContents {
        issuer: Option<String>,
        paths: Option<Vec<BlindedPath>>,
        supported_quantity: Quantity,
-       signing_pubkey: Option<PublicKey>,
+       signing_pubkey: PublicKey,
 }
 
 impl Offer {
@@ -319,13 +321,7 @@ impl Offer {
        /// Whether the offer has expired.
        #[cfg(feature = "std")]
        pub fn is_expired(&self) -> bool {
-               match self.absolute_expiry() {
-                       Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
-                               Ok(elapsed) => elapsed > seconds_from_epoch,
-                               Err(_) => false,
-                       },
-                       None => false,
-               }
+               self.contents.is_expired()
        }
 
        /// The issuer of the offer, possibly beginning with `user@domain` or `domain`. Intended to be
@@ -359,7 +355,7 @@ impl Offer {
 
        /// The public key used by the recipient to sign invoices.
        pub fn signing_pubkey(&self) -> PublicKey {
-               self.contents.signing_pubkey.unwrap()
+               self.contents.signing_pubkey()
        }
 
        /// Creates an [`InvoiceRequest`] for the offer with the given `metadata` and `payer_id`, which
@@ -410,6 +406,17 @@ impl OfferContents {
                self.chains().contains(&chain)
        }
 
+       #[cfg(feature = "std")]
+       pub(super) fn is_expired(&self) -> bool {
+               match self.absolute_expiry {
+                       Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
+                               Ok(elapsed) => elapsed > seconds_from_epoch,
+                               Err(_) => false,
+                       },
+                       None => false,
+               }
+       }
+
        pub fn amount(&self) -> Option<&Amount> {
                self.amount.as_ref()
        }
@@ -473,6 +480,10 @@ impl OfferContents {
                }
        }
 
+       pub(super) fn signing_pubkey(&self) -> PublicKey {
+               self.signing_pubkey
+       }
+
        pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
                let (currency, amount) = match &self.amount {
                        None => (None, None),
@@ -497,7 +508,7 @@ impl OfferContents {
                        paths: self.paths.as_ref(),
                        issuer: self.issuer.as_ref(),
                        quantity_max: self.supported_quantity.to_tlv_record(),
-                       node_id: self.signing_pubkey.as_ref(),
+                       node_id: Some(&self.signing_pubkey),
                }
        }
 }
@@ -567,7 +578,7 @@ tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
        (6, currency: CurrencyCode),
        (8, amount: (u64, HighZeroBytesDroppedBigSize)),
        (10, description: (String, WithoutLength)),
-       (12, features: OfferFeatures),
+       (12, features: (OfferFeatures, WithoutLength)),
        (14, absolute_expiry: (u64, HighZeroBytesDroppedBigSize)),
        (16, paths: (Vec<BlindedPath>, WithoutLength)),
        (18, issuer: (String, WithoutLength)),
@@ -634,13 +645,14 @@ impl TryFrom<OfferTlvStream> for OfferContents {
                        Some(n) => Quantity::Bounded(NonZeroU64::new(n).unwrap()),
                };
 
-               if node_id.is_none() {
-                       return Err(SemanticError::MissingSigningPubkey);
-               }
+               let signing_pubkey = match node_id {
+                       None => return Err(SemanticError::MissingSigningPubkey),
+                       Some(node_id) => node_id,
+               };
 
                Ok(OfferContents {
                        chains, metadata, amount, description, features, absolute_expiry, issuer, paths,
-                       supported_quantity, signing_pubkey: node_id,
+                       supported_quantity, signing_pubkey,
                })
        }
 }
@@ -653,7 +665,7 @@ impl core::fmt::Display for Offer {
 
 #[cfg(test)]
 mod tests {
-       use super::{Amount, Offer, OfferBuilder, Quantity};
+       use super::{Amount, Offer, OfferBuilder, OfferTlvStreamRef, Quantity};
 
        use bitcoin::blockdata::constants::ChainHash;
        use bitcoin::network::constants::Network;
@@ -680,7 +692,7 @@ mod tests {
        #[test]
        fn builds_offer_with_defaults() {
                let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap();
-               let tlv_stream = offer.as_tlv_stream();
+
                let mut buffer = Vec::new();
                offer.write(&mut buffer).unwrap();
 
@@ -699,17 +711,22 @@ mod tests {
                assert_eq!(offer.supported_quantity(), Quantity::one());
                assert_eq!(offer.signing_pubkey(), pubkey(42));
 
-               assert_eq!(tlv_stream.chains, None);
-               assert_eq!(tlv_stream.metadata, None);
-               assert_eq!(tlv_stream.currency, None);
-               assert_eq!(tlv_stream.amount, None);
-               assert_eq!(tlv_stream.description, Some(&String::from("foo")));
-               assert_eq!(tlv_stream.features, None);
-               assert_eq!(tlv_stream.absolute_expiry, None);
-               assert_eq!(tlv_stream.paths, None);
-               assert_eq!(tlv_stream.issuer, None);
-               assert_eq!(tlv_stream.quantity_max, None);
-               assert_eq!(tlv_stream.node_id, Some(&pubkey(42)));
+               assert_eq!(
+                       offer.as_tlv_stream(),
+                       OfferTlvStreamRef {
+                               chains: None,
+                               metadata: None,
+                               currency: None,
+                               amount: None,
+                               description: Some(&String::from("foo")),
+                               features: None,
+                               absolute_expiry: None,
+                               paths: None,
+                               issuer: None,
+                               quantity_max: None,
+                               node_id: Some(&pubkey(42)),
+                       },
+               );
 
                if let Err(e) = Offer::try_from(buffer) {
                        panic!("error parsing offer: {:?}", e);
@@ -1121,11 +1138,13 @@ mod tests {
                        panic!("error parsing offer: {:?}", e);
                }
 
-               let mut builder = OfferBuilder::new("foo".into(), pubkey(42));
-               builder.offer.signing_pubkey = None;
+               let mut tlv_stream = offer.as_tlv_stream();
+               tlv_stream.node_id = None;
 
-               let offer = builder.build().unwrap();
-               match offer.to_string().parse::<Offer>() {
+               let mut encoded_offer = Vec::new();
+               tlv_stream.write(&mut encoded_offer).unwrap();
+
+               match Offer::try_from(encoded_offer) {
                        Ok(_) => panic!("expected error"),
                        Err(e) => {
                                assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey));