@@ -26,7 +26,7 @@ pub use lightning::chain::channelmonitor::BalanceSource;
2626pub use lightning:: events:: { ClosureReason , PaymentFailureReason } ;
2727pub use lightning:: ln:: types:: ChannelId ;
2828pub use lightning:: offers:: invoice:: Bolt12Invoice ;
29- pub use lightning:: offers:: offer:: { Offer , OfferId } ;
29+ pub use lightning:: offers:: offer:: OfferId ;
3030pub use lightning:: offers:: refund:: Refund ;
3131pub use lightning:: routing:: gossip:: { NodeAlias , NodeId , RoutingFees } ;
3232pub use lightning:: util:: string:: UntrustedString ;
@@ -57,6 +57,7 @@ use bitcoin::hashes::sha256::Hash as Sha256;
5757use bitcoin:: hashes:: Hash ;
5858use bitcoin:: secp256k1:: PublicKey ;
5959use lightning:: ln:: channelmanager:: PaymentId ;
60+ use lightning:: offers:: offer:: { Amount as LdkAmount , Offer as LdkOffer } ;
6061use lightning:: util:: ser:: Writeable ;
6162use lightning_invoice:: { Bolt11Invoice as LdkBolt11Invoice , Bolt11InvoiceDescriptionRef } ;
6263
@@ -113,15 +114,89 @@ impl UniffiCustomTypeConverter for Address {
113114 }
114115}
115116
116- impl UniffiCustomTypeConverter for Offer {
117- type Builtin = String ;
117+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
118+ pub enum Amount {
119+ Bitcoin { amount_msats : u64 } ,
120+ Currency { iso4217_code : String , amount : u64 } ,
121+ }
122+
123+ impl From < LdkAmount > for Amount {
124+ fn from ( ldk_amount : LdkAmount ) -> Self {
125+ match ldk_amount {
126+ LdkAmount :: Bitcoin { amount_msats } => Amount :: Bitcoin { amount_msats } ,
127+ LdkAmount :: Currency { iso4217_code, amount } => Amount :: Currency {
128+ iso4217_code : iso4217_code. iter ( ) . map ( |& b| b as char ) . collect ( ) ,
129+ amount,
130+ } ,
131+ }
132+ }
133+ }
118134
119- fn into_custom ( val : Self :: Builtin ) -> uniffi:: Result < Self > {
120- Offer :: from_str ( & val) . map_err ( |_| Error :: InvalidOffer . into ( ) )
135+ /// An `Offer` is a potentially long-lived proposal for payment of a good or service.
136+ ///
137+ /// An offer is a precursor to an [`InvoiceRequest`]. A merchant publishes an offer from which a
138+ /// customer may request an [`Bolt12Invoice`] for a specific quantity and using an amount sufficient
139+ /// to cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
140+ ///
141+ /// Offers may be denominated in currency other than bitcoin but are ultimately paid using the
142+ /// latter.
143+ ///
144+ /// Through the use of [`BlindedMessagePath`]s, offers provide recipient privacy.
145+ ///
146+ /// [`InvoiceRequest`]: lightning::offers::invoice_request::InvoiceRequest
147+ /// [`Bolt12Invoice`]: lightning::offers::invoice::Bolt12Invoice
148+ /// [`Offer`]: lightning::offers::Offer:amount
149+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
150+ pub struct Offer {
151+ pub inner : LdkOffer ,
152+ }
153+
154+ impl Offer {
155+ pub fn from_str ( offer_str : & str ) -> Result < Self , Error > {
156+ offer_str. parse ( )
121157 }
122158
123- fn from_custom ( obj : Self ) -> Self :: Builtin {
124- obj. to_string ( )
159+ /// Returns the id of the offer.
160+ pub fn id ( & self ) -> OfferId {
161+ OfferId ( self . inner . id ( ) . 0 )
162+ }
163+
164+ /// Whether the offer has expired.
165+ pub fn is_expired ( & self ) -> bool {
166+ self . inner . is_expired ( )
167+ }
168+
169+ /// The minimum amount required for a successful payment of a single item.
170+ pub fn description ( & self ) -> Option < String > {
171+ self . inner . description ( ) . map ( |printable| printable. to_string ( ) )
172+ }
173+
174+ /// The issuer of the offer, possibly beginning with `user@domain` or `domain`. Intended to be
175+ /// displayed to the user but with the caveat that it has not been verified in any way.
176+ pub fn issuer ( & self ) -> Option < String > {
177+ self . inner . issuer ( ) . map ( |printable| printable. to_string ( ) )
178+ }
179+
180+ /// The minimum amount required for a successful payment of a single item.
181+ pub fn amount ( & self ) -> Option < Amount > {
182+ self . inner . amount ( ) . map ( |amount| amount. into ( ) )
183+ }
184+ }
185+
186+ impl std:: str:: FromStr for Offer {
187+ type Err = Error ;
188+
189+ fn from_str ( offer_str : & str ) -> Result < Self , Self :: Err > {
190+ offer_str
191+ . parse :: < LdkOffer > ( )
192+ . map ( |offer| Offer { inner : offer } )
193+ . map_err ( |_| Error :: InvalidOffer )
194+ }
195+ }
196+
197+ impl From < LdkOffer > for Offer {
198+ fn from ( offer : LdkOffer ) -> Self {
199+ Offer { inner : offer }
125200 }
126201}
127202
@@ -658,6 +733,10 @@ impl UniffiCustomTypeConverter for DateTime {
658733
659734#[ cfg( test) ]
660735mod tests {
736+ use std:: time:: { SystemTime , UNIX_EPOCH } ;
737+
738+ use lightning:: offers:: offer:: OfferBuilder ;
739+
661740 use super :: * ;
662741
663742 fn create_test_invoice ( ) -> ( LdkBolt11Invoice , Bolt11Invoice ) {
@@ -667,6 +746,27 @@ mod tests {
667746 ( ldk_invoice, wrapped_invoice)
668747 }
669748
749+ fn create_test_offer ( ) -> ( LdkOffer , Offer ) {
750+ let pubkey = bitcoin:: secp256k1:: PublicKey :: from_str (
751+ "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" ,
752+ )
753+ . unwrap ( ) ;
754+
755+ let expiry =
756+ ( SystemTime :: now ( ) + Duration :: from_secs ( 3600 ) ) . duration_since ( UNIX_EPOCH ) . unwrap ( ) ;
757+
758+ let builder = OfferBuilder :: new ( pubkey)
759+ . description ( "Test offer description" . to_string ( ) )
760+ . amount_msats ( 100_000 )
761+ . issuer ( "Offer issuer" . to_string ( ) )
762+ . absolute_expiry ( expiry) ;
763+
764+ let ldk_offer = builder. build ( ) . unwrap ( ) ;
765+ let wrapped_offer = Offer :: from ( ldk_offer. clone ( ) ) ;
766+
767+ ( ldk_offer, wrapped_offer)
768+ }
769+
670770 #[ test]
671771 fn test_invoice_description_conversion ( ) {
672772 let hash = "09d08d4865e8af9266f6cc7c0ae23a1d6bf868207cf8f7c5979b9f6ed850dfb0" . to_string ( ) ;
@@ -776,4 +876,57 @@ mod tests {
776876 parsed_invoice. payment_hash( ) . to_byte_array( ) . to_vec( )
777877 ) ;
778878 }
879+
880+ #[ test]
881+ fn test_offer ( ) {
882+ let ( ldk_offer, wrapped_offer) = create_test_offer ( ) ;
883+ match ( ldk_offer. description ( ) , wrapped_offer. description ( ) ) {
884+ ( Some ( ldk_desc) , Some ( wrapped_desc) ) => {
885+ assert_eq ! ( ldk_desc. to_string( ) , wrapped_desc) ;
886+ } ,
887+ ( None , None ) => {
888+ panic ! ( "Both offers unexpectedly had no description!" ) ;
889+ } ,
890+ ( Some ( _) , None ) => {
891+ panic ! ( "LDK offer had a description but wrapped offer did not!" ) ;
892+ } ,
893+ ( None , Some ( _) ) => {
894+ panic ! ( "Wrapped offer had a description but LDK offer did not!" ) ;
895+ } ,
896+ }
897+
898+ match ( ldk_offer. amount ( ) , wrapped_offer. amount ( ) ) {
899+ ( Some ( ldk_amount) , Some ( wrapped_amount) ) => {
900+ let ldk_amount: Amount = ldk_amount. into ( ) ;
901+ assert_eq ! ( ldk_amount, wrapped_amount) ;
902+ } ,
903+ ( None , None ) => {
904+ panic ! ( "Both offers unexpectedly had no description!" ) ;
905+ } ,
906+ ( Some ( _) , None ) => {
907+ panic ! ( "LDK offer had an amount but wrapped offer did not!" ) ;
908+ } ,
909+ ( None , Some ( _) ) => {
910+ panic ! ( "Wrapped offer had an amount but LDK offer did not!" ) ;
911+ } ,
912+ }
913+
914+ match ( ldk_offer. issuer ( ) , wrapped_offer. issuer ( ) ) {
915+ ( Some ( ldk_issuer) , Some ( wrapped_issuer) ) => {
916+ assert_eq ! ( ldk_issuer. to_string( ) , wrapped_issuer) ;
917+ } ,
918+ ( None , None ) => {
919+ panic ! ( "Both offers unexpectedly had no issuer!" ) ;
920+ } ,
921+ ( Some ( _) , None ) => {
922+ panic ! ( "LDK offer had an issuer but wrapped offer did not!" ) ;
923+ } ,
924+ ( None , Some ( _) ) => {
925+ panic ! ( "Wrapped offer had an issuer but LDK offer did not!" ) ;
926+ } ,
927+ }
928+
929+ assert_eq ! ( ldk_offer. is_expired( ) , wrapped_offer. is_expired( ) ) ;
930+ assert_eq ! ( ldk_offer. id( ) , wrapped_offer. id( ) ) ;
931+ }
779932}
0 commit comments