//! published as a QR code to be scanned by a customer. The customer uses the offer to request an
//! invoice from the merchant to be paid.
//!
+//! # Example
+//!
//! ```
//! extern crate bitcoin;
//! extern crate core;
//! # Ok(())
//! # }
//! ```
+//!
+//! # Note
+//!
+//! If constructing an [`Offer`] for use with a [`ChannelManager`], use
+//! [`ChannelManager::create_offer_builder`] instead of [`OfferBuilder::new`].
+//!
+//! [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
+//! [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::network::constants::Network;
/// while the offer is valid.
///
/// Use a different pubkey per offer to avoid correlating offers.
+ ///
+ /// # Note
+ ///
+ /// If constructing an [`Offer`] for use with a [`ChannelManager`], use
+ /// [`ChannelManager::create_offer_builder`] instead of [`OfferBuilder::new`].
+ ///
+ /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
+ /// [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder
pub fn new(description: String, signing_pubkey: PublicKey) -> Self {
OfferBuilder {
offer: OfferContents {
/// See [`Offer::chains`] on how this relates to the payment currency.
///
/// Successive calls to this method will add another chain hash.
- pub fn chain(mut self, network: Network) -> Self {
+ pub fn chain(self, network: Network) -> Self {
+ self.chain_hash(ChainHash::using_genesis_block(network))
+ }
+
+ /// Adds the [`ChainHash`] to [`Offer::chains`]. If not called, the chain hash of
+ /// [`Network::Bitcoin`] is assumed to be the only one supported.
+ ///
+ /// See [`Offer::chains`] on how this relates to the payment currency.
+ ///
+ /// Successive calls to this method will add another chain hash.
+ pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Self {
let chains = self.offer.chains.get_or_insert_with(Vec::new);
- let chain = ChainHash::using_genesis_block(network);
if !chains.contains(&chain) {
chains.push(chain);
}
/// The chains that may be used when paying a requested invoice (e.g., bitcoin mainnet).
/// Payments must be denominated in units of the minimal lightning-payable unit (e.g., msats)
/// for the selected chain.
- pub fn chains(&$self) -> Vec<$crate::bitcoin::blockdata::constants::ChainHash> {
+ pub fn chains(&$self) -> Vec<bitcoin::blockdata::constants::ChainHash> {
$contents.chains()
}
}
/// The public key used by the recipient to sign invoices.
- pub fn signing_pubkey(&$self) -> $crate::bitcoin::secp256k1::PublicKey {
+ pub fn signing_pubkey(&$self) -> bitcoin::secp256k1::PublicKey {
$contents.signing_pubkey()
}
} }
#[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,
- }
+ SystemTime::UNIX_EPOCH
+ .elapsed()
+ .map(|duration_since_epoch| self.is_expired_no_std(duration_since_epoch))
+ .unwrap_or(false)
+ }
+
+ pub(super) fn is_expired_no_std(&self, duration_since_epoch: Duration) -> bool {
+ self.absolute_expiry
+ .map(|absolute_expiry| duration_since_epoch > absolute_expiry)
+ .unwrap_or(false)
}
pub fn issuer(&self) -> Option<PrintableString> {
}
}
}
-
-#[cfg(test)]
-mod bech32_tests {
- use super::{Bolt12ParseError, Offer};
- use bitcoin::bech32;
- use crate::ln::msgs::DecodeError;
-
- #[test]
- fn encodes_offer_as_bech32_without_checksum() {
- let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
- let offer = dbg!(encoded_offer.parse::<Offer>().unwrap());
- let reencoded_offer = offer.to_string();
- dbg!(reencoded_offer.parse::<Offer>().unwrap());
- assert_eq!(reencoded_offer, encoded_offer);
- }
-
- #[test]
- fn parses_bech32_encoded_offers() {
- let offers = [
- // BOLT 12 test vectors
- "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
- "l+no1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
- "lno1pqps7sjqpgt+yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+5k7msjzfpy7nz5yqcn+ygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+5xvxg",
- "lno1pqps7sjqpgt+ yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+ 5k7msjzfpy7nz5yqcn+\nygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+\r\n 5xvxg",
- ];
- for encoded_offer in &offers {
- if let Err(e) = encoded_offer.parse::<Offer>() {
- panic!("Invalid offer ({:?}): {}", e, encoded_offer);
- }
- }
- }
-
- #[test]
- fn fails_parsing_bech32_encoded_offers_with_invalid_continuations() {
- let offers = [
- // BOLT 12 test vectors
- "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+",
- "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+ ",
- "+lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
- "+ lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
- "ln++o1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
- ];
- for encoded_offer in &offers {
- match encoded_offer.parse::<Offer>() {
- Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, Bolt12ParseError::InvalidContinuation),
- }
- }
-
- }
-
- #[test]
- fn fails_parsing_bech32_encoded_offer_with_invalid_hrp() {
- let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
- match encoded_offer.parse::<Offer>() {
- Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, Bolt12ParseError::InvalidBech32Hrp),
- }
- }
-
- #[test]
- fn fails_parsing_bech32_encoded_offer_with_invalid_bech32_data() {
- let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
- match encoded_offer.parse::<Offer>() {
- Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(bech32::Error::InvalidChar('o'))),
- }
- }
-
- #[test]
- fn fails_parsing_bech32_encoded_offer_with_invalid_tlv_data() {
- let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
- match encoded_offer.parse::<Offer>() {
- Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
- }
- }
-}