1 // This file is Copyright its original authors, visible in version control
4 // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5 // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7 // You may not use this file except in accordance with one or both of these
10 //! Data structures and encoding for `offer` messages.
12 //! An [`Offer`] represents an "offer to be paid." It is typically constructed by a merchant and
13 //! published as a QR code to be scanned by a customer. The customer uses the offer to request an
14 //! invoice from the merchant to be paid.
17 //! extern crate bitcoin;
18 //! extern crate core;
19 //! extern crate lightning;
21 //! use core::num::NonZeroU64;
22 //! use core::time::Duration;
24 //! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
25 //! use lightning::offers::offer::{Amount, OfferBuilder};
27 //! # use bitcoin::secp256k1;
28 //! # use lightning::onion_message::BlindedPath;
29 //! # #[cfg(feature = "std")]
30 //! # use std::time::SystemTime;
32 //! # fn create_blinded_path() -> BlindedPath { unimplemented!() }
33 //! # fn create_another_blinded_path() -> BlindedPath { unimplemented!() }
35 //! # #[cfg(feature = "std")]
36 //! # fn build() -> Result<(), secp256k1::Error> {
37 //! let secp_ctx = Secp256k1::new();
38 //! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?);
39 //! let pubkey = PublicKey::from(keys);
41 //! let one_item = NonZeroU64::new(1).unwrap();
42 //! let expiration = SystemTime::now() + Duration::from_secs(24 * 60 * 60);
43 //! let offer = OfferBuilder::new("coffee, large".to_string(), pubkey)
44 //! .amount(Amount::Bitcoin { amount_msats: 20_000 })
45 //! .quantity_range(one_item..)
46 //! .absolute_expiry(expiration.duration_since(SystemTime::UNIX_EPOCH).unwrap())
47 //! .issuer("Foo Bar".to_string())
48 //! .path(create_blinded_path())
49 //! .path(create_another_blinded_path())
56 use bitcoin::blockdata::constants::ChainHash;
57 use bitcoin::network::constants::Network;
58 use bitcoin::secp256k1::PublicKey;
59 use core::num::NonZeroU64;
60 use core::ops::{Bound, RangeBounds};
61 use core::time::Duration;
63 use ln::features::OfferFeatures;
64 use ln::msgs::MAX_VALUE_MSAT;
65 use onion_message::BlindedPath;
66 use util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer};
70 #[cfg(feature = "std")]
71 use std::time::SystemTime;
73 /// Builds an [`Offer`] for the "offer to be paid" flow.
75 /// See [module-level documentation] for usage.
77 /// [module-level documentation]: self
78 pub struct OfferBuilder {
83 /// Creates a new builder for an offer setting the [`Offer::description`] and using the
84 /// [`Offer::signing_pubkey`] for signing invoices. The associated secret key must be remembered
85 /// while the offer is valid.
87 /// Use a different pubkey per offer to avoid correlating offers.
88 pub fn new(description: String, signing_pubkey: PublicKey) -> Self {
89 let offer = OfferContents {
90 chains: None, metadata: None, amount: None, description,
91 features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
92 quantity_min: None, quantity_max: None, signing_pubkey: Some(signing_pubkey),
94 OfferBuilder { offer }
97 /// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called,
98 /// the chain hash of [`Network::Bitcoin`] is assumed to be the only one supported.
100 /// Successive calls to this method will add another chain hash.
101 pub fn chain(mut self, network: Network) -> Self {
102 let chains = self.offer.chains.get_or_insert_with(Vec::new);
103 let chain = ChainHash::using_genesis_block(network);
104 if !chains.contains(&chain) {
111 /// Sets the [`Offer::metadata`].
113 /// Successive calls to this method will override the previous setting.
114 pub fn metadata(mut self, metadata: Vec<u8>) -> Self {
115 self.offer.metadata = Some(metadata);
119 /// Sets the [`Offer::amount`].
121 /// Successive calls to this method will override the previous setting.
122 pub fn amount(mut self, amount: Amount) -> Self {
123 self.offer.amount = Some(amount);
127 /// Sets the [`Offer::features`].
129 /// Successive calls to this method will override the previous setting.
131 pub fn features(mut self, features: OfferFeatures) -> Self {
132 self.offer.features = features;
136 /// Sets the [`Offer::absolute_expiry`] as seconds since the Unix epoch. Any expiry that has
137 /// already passed is valid and can be checked for using [`Offer::is_expired`].
139 /// Successive calls to this method will override the previous setting.
140 pub fn absolute_expiry(mut self, absolute_expiry: Duration) -> Self {
141 self.offer.absolute_expiry = Some(absolute_expiry);
145 /// Sets the [`Offer::issuer`].
147 /// Successive calls to this method will override the previous setting.
148 pub fn issuer(mut self, issuer: String) -> Self {
149 self.offer.issuer = Some(issuer);
153 /// Adds a blinded path to [`Offer::paths`]. Must include one path if only connected by private
156 /// Successive calls to this method will add another blinded path. Caller is responsible for not
157 /// adding duplicate paths.
158 pub fn path(mut self, path: BlindedPath) -> Self {
159 self.offer.paths.get_or_insert_with(Vec::new).push(path);
163 /// Sets a fixed quantity of items for [`Offer::quantity_min`] and [`Offer::quantity_max`]. If
164 /// not set, `1` is assumed.
166 /// Successive calls to this method or [`quantity_range`] will override the previous setting.
168 /// [`quantity_range`]: Self::quantity_range
169 pub fn quantity_fixed(mut self, quantity: NonZeroU64) -> Self {
170 let quantity = Some(quantity.get()).filter(|quantity| *quantity != 1);
171 self.offer.quantity_min = quantity;
172 self.offer.quantity_max = quantity;
176 /// Sets a quantity range of items for [`Offer::quantity_min`] and [`Offer::quantity_max`]. If
177 /// not set, `1` is assumed.
179 /// Successive calls to this method or [`quantity_fixed`] will override the previous setting.
181 /// [`quantity_fixed`]: Self::quantity_fixed
182 pub fn quantity_range<R: RangeBounds<NonZeroU64>>(mut self, quantity: R) -> Self {
183 self.offer.quantity_min = match quantity.start_bound() {
184 Bound::Included(n) => Some(n.get()),
185 Bound::Excluded(_) => panic!("Bound::Excluded not supported for start_bound"),
186 Bound::Unbounded => Some(1),
188 self.offer.quantity_max = match quantity.end_bound() {
189 Bound::Included(n) => Some(n.get()),
190 Bound::Excluded(n) => Some(n.get() - 1),
191 Bound::Unbounded => None,
194 // Use a minimal encoding whenever 1 can be inferred.
195 if let Some(1) = self.offer.quantity_min {
196 match self.offer.quantity_max {
198 self.offer.quantity_min = None;
199 self.offer.quantity_max = None;
202 self.offer.quantity_min = None;
211 /// Builds an [`Offer`] from the builder's settings.
212 pub fn build(self) -> Result<Offer, ()> {
213 if let Some(Amount::Currency { .. }) = self.offer.amount {
217 if self.offer.amount_msats() > MAX_VALUE_MSAT {
221 if self.offer.quantity_min() > self.offer.quantity_max() {
225 let mut bytes = Vec::new();
226 self.offer.write(&mut bytes).unwrap();
230 contents: self.offer,
235 /// An `Offer` is a potentially long-lived proposal for payment of a good or service.
237 /// An offer is precursor to an `InvoiceRequest`. A merchant publishes an offer from which a
238 /// customer may request an `Invoice` for a specific quantity and using an amount enough to cover
239 /// that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
241 /// Offers may be denominated in currency other than bitcoin but are ultimately paid using the
244 /// Through the use of [`BlindedPath`]s, offers provide recipient privacy.
245 #[derive(Clone, Debug)]
247 // The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
250 contents: OfferContents,
253 /// The contents of an [`Offer`], which may be shared with an `InvoiceRequest` or an `Invoice`.
254 #[derive(Clone, Debug)]
255 pub(crate) struct OfferContents {
256 chains: Option<Vec<ChainHash>>,
257 metadata: Option<Vec<u8>>,
258 amount: Option<Amount>,
260 features: OfferFeatures,
261 absolute_expiry: Option<Duration>,
262 issuer: Option<String>,
263 paths: Option<Vec<BlindedPath>>,
264 quantity_min: Option<u64>,
265 quantity_max: Option<u64>,
266 signing_pubkey: Option<PublicKey>,
270 // TODO: Return a slice once ChainHash has constants.
271 // - https://github.com/rust-bitcoin/rust-bitcoin/pull/1283
272 // - https://github.com/rust-bitcoin/rust-bitcoin/pull/1286
273 /// The chains that may be used when paying a requested invoice.
274 pub fn chains(&self) -> Vec<ChainHash> {
278 .unwrap_or_else(|| vec![ChainHash::using_genesis_block(Network::Bitcoin)])
281 // TODO: Link to corresponding method in `InvoiceRequest`.
282 /// Metadata set by the originator. Useful for authentication and validating fields since it is
283 /// reflected in `invoice_request` messages along with all the other fields from the `offer`.
284 pub fn metadata(&self) -> Option<&Vec<u8>> {
285 self.contents.metadata.as_ref()
288 /// The minimum amount required for a successful payment of a single item.
289 pub fn amount(&self) -> Option<&Amount> {
290 self.contents.amount.as_ref()
293 /// A complete description of the purpose of the payment.
294 pub fn description(&self) -> &str {
295 &self.contents.description
298 /// Features pertaining to the offer.
299 pub fn features(&self) -> &OfferFeatures {
300 &self.contents.features
303 /// Duration since the Unix epoch when an invoice should no longer be requested.
305 /// If `None`, the offer does not expire.
306 pub fn absolute_expiry(&self) -> Option<Duration> {
307 self.contents.absolute_expiry
310 /// Whether the offer has expired.
311 #[cfg(feature = "std")]
312 pub fn is_expired(&self) -> bool {
313 match self.absolute_expiry() {
314 Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
315 Ok(elapsed) => elapsed > seconds_from_epoch,
322 /// The issuer of the offer, possibly beginning with `user@domain` or `domain`. Intended to be
323 /// displayed to the user, so should be human-readable.
324 pub fn issuer(&self) -> Option<&str> {
325 self.contents.issuer.as_ref().map(|issuer| issuer.as_str())
328 /// Paths to the recipient originating from publicly reachable nodes. Blinded paths provide
329 /// recipient privacy by obfuscating its node id.
330 pub fn paths(&self) -> &[BlindedPath] {
331 self.contents.paths.as_ref().map(|paths| paths.as_slice()).unwrap_or(&[])
334 /// The minimum quantity of items supported.
335 pub fn quantity_min(&self) -> u64 {
336 self.contents.quantity_min()
339 /// The maximum quantity of items supported.
340 pub fn quantity_max(&self) -> u64 {
341 self.contents.quantity_max()
344 /// The public key used by the recipient to sign invoices.
345 pub fn signing_pubkey(&self) -> PublicKey {
346 self.contents.signing_pubkey.unwrap()
350 fn as_tlv_stream(&self) -> OfferTlvStreamRef {
351 self.contents.as_tlv_stream()
356 pub fn amount_msats(&self) -> u64 {
357 self.amount.as_ref().map(Amount::as_msats).unwrap_or(0)
360 pub fn quantity_min(&self) -> u64 {
361 self.quantity_min.unwrap_or(1)
364 pub fn quantity_max(&self) -> u64 {
365 self.quantity_max.unwrap_or_else(||
366 self.quantity_min.map_or(1, |_| u64::max_value()))
369 fn as_tlv_stream(&self) -> OfferTlvStreamRef {
370 let (currency, amount) = match &self.amount {
371 None => (None, None),
372 Some(Amount::Bitcoin { amount_msats }) => (None, Some(*amount_msats)),
373 Some(Amount::Currency { iso4217_code, amount }) => (
374 Some(iso4217_code), Some(*amount)
379 if self.features == OfferFeatures::empty() { None } else { Some(&self.features) }
383 chains: self.chains.as_ref(),
384 metadata: self.metadata.as_ref(),
387 description: Some(&self.description),
389 absolute_expiry: self.absolute_expiry.map(|duration| duration.as_secs()),
390 paths: self.paths.as_ref(),
391 issuer: self.issuer.as_ref(),
392 quantity_min: self.quantity_min,
393 quantity_max: self.quantity_max,
394 node_id: self.signing_pubkey.as_ref(),
399 impl Writeable for OfferContents {
400 fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
401 self.as_tlv_stream().write(writer)
405 /// The minimum amount required for an item in an [`Offer`], denominated in either bitcoin or
406 /// another currency.
407 #[derive(Clone, Debug, PartialEq)]
409 /// An amount of bitcoin.
411 /// The amount in millisatoshi.
414 /// An amount of currency specified using ISO 4712.
416 /// The currency that the amount is denominated in.
417 iso4217_code: CurrencyCode,
418 /// The amount in the currency unit adjusted by the ISO 4712 exponent (e.g., USD cents).
424 /// Returns the amount in millisatoshi.
425 pub fn as_msats(&self) -> u64 {
427 Amount::Currency { .. } => unimplemented!(),
428 Amount::Bitcoin { amount_msats } => *amount_msats,
433 /// An ISO 4712 three-letter currency code (e.g., USD).
434 pub type CurrencyCode = [u8; 3];
436 tlv_stream!(OfferTlvStream, OfferTlvStreamRef, {
437 (2, chains: (Vec<ChainHash>, WithoutLength)),
438 (4, metadata: (Vec<u8>, WithoutLength)),
439 (6, currency: CurrencyCode),
440 (8, amount: (u64, HighZeroBytesDroppedBigSize)),
441 (10, description: (String, WithoutLength)),
442 (12, features: OfferFeatures),
443 (14, absolute_expiry: (u64, HighZeroBytesDroppedBigSize)),
444 (16, paths: (Vec<BlindedPath>, WithoutLength)),
445 (18, issuer: (String, WithoutLength)),
446 (20, quantity_min: (u64, HighZeroBytesDroppedBigSize)),
447 (22, quantity_max: (u64, HighZeroBytesDroppedBigSize)),
448 (24, node_id: PublicKey),
453 use super::{Amount, OfferBuilder};
455 use bitcoin::blockdata::constants::ChainHash;
456 use bitcoin::network::constants::Network;
457 use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
458 use core::num::NonZeroU64;
459 use core::time::Duration;
460 use ln::features::OfferFeatures;
461 use ln::msgs::MAX_VALUE_MSAT;
462 use onion_message::{BlindedHop, BlindedPath};
463 use util::ser::Writeable;
465 fn pubkey(byte: u8) -> PublicKey {
466 let secp_ctx = Secp256k1::new();
467 PublicKey::from_secret_key(&secp_ctx, &privkey(byte))
470 fn privkey(byte: u8) -> SecretKey {
471 SecretKey::from_slice(&[byte; 32]).unwrap()
475 fn builds_offer_with_defaults() {
476 let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap();
477 let tlv_stream = offer.as_tlv_stream();
478 let mut buffer = Vec::new();
479 offer.contents.write(&mut buffer).unwrap();
481 assert_eq!(offer.bytes, buffer.as_slice());
482 assert_eq!(offer.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]);
483 assert_eq!(offer.metadata(), None);
484 assert_eq!(offer.amount(), None);
485 assert_eq!(offer.description(), "foo");
486 assert_eq!(offer.features(), &OfferFeatures::empty());
487 assert_eq!(offer.absolute_expiry(), None);
488 #[cfg(feature = "std")]
489 assert!(!offer.is_expired());
490 assert_eq!(offer.paths(), &[]);
491 assert_eq!(offer.issuer(), None);
492 assert_eq!(offer.quantity_min(), 1);
493 assert_eq!(offer.quantity_max(), 1);
494 assert_eq!(offer.signing_pubkey(), pubkey(42));
496 assert_eq!(tlv_stream.chains, None);
497 assert_eq!(tlv_stream.metadata, None);
498 assert_eq!(tlv_stream.currency, None);
499 assert_eq!(tlv_stream.amount, None);
500 assert_eq!(tlv_stream.description, Some(&String::from("foo")));
501 assert_eq!(tlv_stream.features, None);
502 assert_eq!(tlv_stream.absolute_expiry, None);
503 assert_eq!(tlv_stream.paths, None);
504 assert_eq!(tlv_stream.issuer, None);
505 assert_eq!(tlv_stream.quantity_min, None);
506 assert_eq!(tlv_stream.quantity_max, None);
507 assert_eq!(tlv_stream.node_id, Some(&pubkey(42)));
511 fn builds_offer_with_chains() {
512 let chain = ChainHash::using_genesis_block(Network::Bitcoin);
514 ChainHash::using_genesis_block(Network::Bitcoin),
515 ChainHash::using_genesis_block(Network::Testnet),
518 let offer = OfferBuilder::new("foo".into(), pubkey(42))
519 .chain(Network::Bitcoin)
522 assert_eq!(offer.chains(), vec![chain]);
523 assert_eq!(offer.as_tlv_stream().chains, Some(&vec![chain]));
525 let offer = OfferBuilder::new("foo".into(), pubkey(42))
526 .chain(Network::Bitcoin)
527 .chain(Network::Bitcoin)
530 assert_eq!(offer.chains(), vec![chain]);
531 assert_eq!(offer.as_tlv_stream().chains, Some(&vec![chain]));
533 let offer = OfferBuilder::new("foo".into(), pubkey(42))
534 .chain(Network::Bitcoin)
535 .chain(Network::Testnet)
538 assert_eq!(offer.chains(), chains);
539 assert_eq!(offer.as_tlv_stream().chains, Some(&chains));
543 fn builds_offer_with_metadata() {
544 let offer = OfferBuilder::new("foo".into(), pubkey(42))
545 .metadata(vec![42; 32])
548 assert_eq!(offer.metadata(), Some(&vec![42; 32]));
549 assert_eq!(offer.as_tlv_stream().metadata, Some(&vec![42; 32]));
551 let offer = OfferBuilder::new("foo".into(), pubkey(42))
552 .metadata(vec![42; 32])
553 .metadata(vec![43; 32])
556 assert_eq!(offer.metadata(), Some(&vec![43; 32]));
557 assert_eq!(offer.as_tlv_stream().metadata, Some(&vec![43; 32]));
561 fn builds_offer_with_amount() {
562 let bitcoin_amount = Amount::Bitcoin { amount_msats: 1000 };
563 let currency_amount = Amount::Currency { iso4217_code: *b"USD", amount: 10 };
565 let offer = OfferBuilder::new("foo".into(), pubkey(42))
566 .amount(bitcoin_amount.clone())
569 let tlv_stream = offer.as_tlv_stream();
570 assert_eq!(offer.amount(), Some(&bitcoin_amount));
571 assert_eq!(tlv_stream.amount, Some(1000));
572 assert_eq!(tlv_stream.currency, None);
574 let builder = OfferBuilder::new("foo".into(), pubkey(42))
575 .amount(currency_amount.clone());
576 let tlv_stream = builder.offer.as_tlv_stream();
577 assert_eq!(builder.offer.amount.as_ref(), Some(¤cy_amount));
578 assert_eq!(tlv_stream.amount, Some(10));
579 assert_eq!(tlv_stream.currency, Some(b"USD"));
580 match builder.build() {
581 Ok(_) => panic!("expected error"),
582 Err(e) => assert_eq!(e, ()),
585 let offer = OfferBuilder::new("foo".into(), pubkey(42))
586 .amount(currency_amount.clone())
587 .amount(bitcoin_amount.clone())
590 let tlv_stream = offer.as_tlv_stream();
591 assert_eq!(tlv_stream.amount, Some(1000));
592 assert_eq!(tlv_stream.currency, None);
594 let invalid_amount = Amount::Bitcoin { amount_msats: MAX_VALUE_MSAT + 1 };
595 match OfferBuilder::new("foo".into(), pubkey(42)).amount(invalid_amount).build() {
596 Ok(_) => panic!("expected error"),
597 Err(e) => assert_eq!(e, ()),
602 fn builds_offer_with_features() {
603 let offer = OfferBuilder::new("foo".into(), pubkey(42))
604 .features(OfferFeatures::unknown())
607 assert_eq!(offer.features(), &OfferFeatures::unknown());
608 assert_eq!(offer.as_tlv_stream().features, Some(&OfferFeatures::unknown()));
610 let offer = OfferBuilder::new("foo".into(), pubkey(42))
611 .features(OfferFeatures::unknown())
612 .features(OfferFeatures::empty())
615 assert_eq!(offer.features(), &OfferFeatures::empty());
616 assert_eq!(offer.as_tlv_stream().features, None);
620 fn builds_offer_with_absolute_expiry() {
621 let future_expiry = Duration::from_secs(u64::max_value());
622 let past_expiry = Duration::from_secs(0);
624 let offer = OfferBuilder::new("foo".into(), pubkey(42))
625 .absolute_expiry(future_expiry)
628 #[cfg(feature = "std")]
629 assert!(!offer.is_expired());
630 assert_eq!(offer.absolute_expiry(), Some(future_expiry));
631 assert_eq!(offer.as_tlv_stream().absolute_expiry, Some(future_expiry.as_secs()));
633 let offer = OfferBuilder::new("foo".into(), pubkey(42))
634 .absolute_expiry(future_expiry)
635 .absolute_expiry(past_expiry)
638 #[cfg(feature = "std")]
639 assert!(offer.is_expired());
640 assert_eq!(offer.absolute_expiry(), Some(past_expiry));
641 assert_eq!(offer.as_tlv_stream().absolute_expiry, Some(past_expiry.as_secs()));
645 fn builds_offer_with_paths() {
648 introduction_node_id: pubkey(40),
649 blinding_point: pubkey(41),
651 BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
652 BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
656 introduction_node_id: pubkey(40),
657 blinding_point: pubkey(41),
659 BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
660 BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
665 let offer = OfferBuilder::new("foo".into(), pubkey(42))
666 .path(paths[0].clone())
667 .path(paths[1].clone())
670 let tlv_stream = offer.as_tlv_stream();
671 assert_eq!(offer.paths(), paths.as_slice());
672 assert_eq!(offer.signing_pubkey(), pubkey(42));
673 assert_ne!(pubkey(42), pubkey(44));
674 assert_eq!(tlv_stream.paths, Some(&paths));
675 assert_eq!(tlv_stream.node_id, Some(&pubkey(42)));
679 fn builds_offer_with_issuer() {
680 let offer = OfferBuilder::new("foo".into(), pubkey(42))
681 .issuer("bar".into())
684 assert_eq!(offer.issuer(), Some("bar"));
685 assert_eq!(offer.as_tlv_stream().issuer, Some(&String::from("bar")));
687 let offer = OfferBuilder::new("foo".into(), pubkey(42))
688 .issuer("bar".into())
689 .issuer("baz".into())
692 assert_eq!(offer.issuer(), Some("baz"));
693 assert_eq!(offer.as_tlv_stream().issuer, Some(&String::from("baz")));
697 fn builds_offer_with_fixed_quantity() {
698 let one = NonZeroU64::new(1).unwrap();
699 let five = NonZeroU64::new(5).unwrap();
700 let ten = NonZeroU64::new(10).unwrap();
702 let offer = OfferBuilder::new("foo".into(), pubkey(42))
706 let tlv_stream = offer.as_tlv_stream();
707 assert_eq!(offer.quantity_min(), 1);
708 assert_eq!(offer.quantity_max(), 1);
709 assert_eq!(tlv_stream.quantity_min, None);
710 assert_eq!(tlv_stream.quantity_max, None);
712 let offer = OfferBuilder::new("foo".into(), pubkey(42))
716 let tlv_stream = offer.as_tlv_stream();
717 assert_eq!(offer.quantity_min(), 10);
718 assert_eq!(offer.quantity_max(), 10);
719 assert_eq!(tlv_stream.quantity_min, Some(10));
720 assert_eq!(tlv_stream.quantity_max, Some(10));
722 let offer = OfferBuilder::new("foo".into(), pubkey(42))
724 .quantity_fixed(five)
727 let tlv_stream = offer.as_tlv_stream();
728 assert_eq!(offer.quantity_min(), 5);
729 assert_eq!(offer.quantity_max(), 5);
730 assert_eq!(tlv_stream.quantity_min, Some(5));
731 assert_eq!(tlv_stream.quantity_max, Some(5));
733 let offer = OfferBuilder::new("foo".into(), pubkey(42))
734 .quantity_range(..ten)
735 .quantity_fixed(five)
738 let tlv_stream = offer.as_tlv_stream();
739 assert_eq!(offer.quantity_min(), 5);
740 assert_eq!(offer.quantity_max(), 5);
741 assert_eq!(tlv_stream.quantity_min, Some(5));
742 assert_eq!(tlv_stream.quantity_max, Some(5));
746 fn builds_offer_with_quantity_range() {
747 let one = NonZeroU64::new(1).unwrap();
748 let five = NonZeroU64::new(5).unwrap();
749 let ten = NonZeroU64::new(10).unwrap();
751 let offer = OfferBuilder::new("foo".into(), pubkey(42))
755 let tlv_stream = offer.as_tlv_stream();
756 assert_eq!(offer.quantity_min(), 1);
757 assert_eq!(offer.quantity_max(), u64::max_value());
758 assert_eq!(tlv_stream.quantity_min, Some(1));
759 assert_eq!(tlv_stream.quantity_max, None);
761 let offer = OfferBuilder::new("foo".into(), pubkey(42))
762 .quantity_range(..ten)
765 let tlv_stream = offer.as_tlv_stream();
766 assert_eq!(offer.quantity_min(), 1);
767 assert_eq!(offer.quantity_max(), 9);
768 assert_eq!(tlv_stream.quantity_min, None);
769 assert_eq!(tlv_stream.quantity_max, Some(9));
771 let offer = OfferBuilder::new("foo".into(), pubkey(42))
772 .quantity_range(one..ten)
775 let tlv_stream = offer.as_tlv_stream();
776 assert_eq!(offer.quantity_min(), 1);
777 assert_eq!(offer.quantity_max(), 9);
778 assert_eq!(tlv_stream.quantity_min, None);
779 assert_eq!(tlv_stream.quantity_max, Some(9));
781 let offer = OfferBuilder::new("foo".into(), pubkey(42))
782 .quantity_range(five..=ten)
785 let tlv_stream = offer.as_tlv_stream();
786 assert_eq!(offer.quantity_min(), 5);
787 assert_eq!(offer.quantity_max(), 10);
788 assert_eq!(tlv_stream.quantity_min, Some(5));
789 assert_eq!(tlv_stream.quantity_max, Some(10));
791 let one = NonZeroU64::new(1).unwrap();
792 let offer = OfferBuilder::new("foo".into(), pubkey(42))
793 .quantity_range(one..=one)
796 let tlv_stream = offer.as_tlv_stream();
797 assert_eq!(offer.quantity_min(), 1);
798 assert_eq!(offer.quantity_max(), 1);
799 assert_eq!(tlv_stream.quantity_min, None);
800 assert_eq!(tlv_stream.quantity_max, None);
802 let offer = OfferBuilder::new("foo".into(), pubkey(42))
803 .quantity_range(five..=five)
806 let tlv_stream = offer.as_tlv_stream();
807 assert_eq!(offer.quantity_min(), 5);
808 assert_eq!(offer.quantity_max(), 5);
809 assert_eq!(tlv_stream.quantity_min, Some(5));
810 assert_eq!(tlv_stream.quantity_max, Some(5));
812 let offer = OfferBuilder::new("foo".into(), pubkey(42))
813 .quantity_fixed(five)
814 .quantity_range(..ten)
817 let tlv_stream = offer.as_tlv_stream();
818 assert_eq!(offer.quantity_min(), 1);
819 assert_eq!(offer.quantity_max(), 9);
820 assert_eq!(tlv_stream.quantity_min, None);
821 assert_eq!(tlv_stream.quantity_max, Some(9));
823 assert!(OfferBuilder::new("foo".into(), pubkey(42))
824 .quantity_range(ten..five)