Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 344ab0b

Browse files
committedJul 5, 2024··
Squash and rebase Bolt12 implementation
1 parent 8c27e8b commit 344ab0b

23 files changed

+2357
-210
lines changed
 

‎libs/Cargo.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ base64 = "0.13.0"
2323
bitcoin = "=0.29.2" # Same version as used in gl-client
2424
# Pin the reqwest dependency until macOS linker issue is fixed: https://github.com/seanmonstar/reqwest/issues/2006
2525
hex = "0.4"
26-
lightning = "=0.0.116" # Same version as used in gl-client
27-
lightning-invoice = "=0.24.0" # Same version as used in gl-client
26+
lightning = "=0.0.118" # Same version as used in gl-client
27+
lightning-invoice = "=0.26.0" # Same version as used in gl-client
2828
log = "0.4"
29-
mockito = "1"
29+
mockito = "1.4.0"
3030
once_cell = "1"
3131
prost = "^0.11"
3232
regex = "1.8.1"

‎libs/sdk-bindings/src/breez_sdk.udl

+40-1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ enum SendPaymentError {
9191
"InvalidInvoice",
9292
"InvoiceExpired",
9393
"InvalidNetwork",
94+
"OfferChanged",
9495
"PaymentFailed",
9596
"PaymentTimeout",
9697
"RouteNotFound",
@@ -481,6 +482,21 @@ dictionary StaticBackupResponse {
481482
sequence<string>? backup;
482483
};
483484

485+
dictionary CreateOfferRequest {
486+
string description;
487+
u64? amount_msat = null;
488+
u64? absolute_expiry = null;
489+
u64? quantity_max = null;
490+
};
491+
492+
dictionary PayOfferRequest {
493+
string offer;
494+
u64? amount_msat = null;
495+
f64? timeout = null;
496+
string? payer_note = null;
497+
string? label = null;
498+
};
499+
484500
dictionary ReceiveOnchainRequest {
485501
OpeningFeeParams? opening_fee_params = null;
486502
};
@@ -716,10 +732,27 @@ dictionary RecommendedFees {
716732
u64 minimum_fee;
717733
};
718734

735+
[Enum]
736+
interface Amount {
737+
Bitcoin(u64 amount_msat);
738+
Currency(string iso4217_code, u64 fractional_amount);
739+
};
740+
741+
dictionary LNOffer {
742+
string bolt12;
743+
sequence<string> chains;
744+
string description;
745+
string signing_pubkey;
746+
Amount? amount;
747+
u64? absolute_expiry;
748+
string? issuer;
749+
};
750+
719751
[Enum]
720752
interface InputType {
721753
BitcoinAddress(BitcoinAddressData address);
722754
Bolt11(LNInvoice invoice);
755+
Bolt12Offer(LNOffer offer);
723756
NodeId(string node_id);
724757
Url(string url);
725758
LnUrlPay(LnUrlPayRequestData data);
@@ -752,7 +785,7 @@ dictionary RedeemOnchainFundsResponse {
752785
};
753786

754787
dictionary SendPaymentRequest {
755-
string bolt11;
788+
string invoice;
756789
u64? amount_msat = null;
757790
string? label = null;
758791
};
@@ -967,6 +1000,12 @@ interface BlockingBreezServices {
9671000

9681001
[Throws=SdkError]
9691002
PrepareRedeemOnchainFundsResponse prepare_redeem_onchain_funds(PrepareRedeemOnchainFundsRequest req);
1003+
1004+
[Throws=SdkError]
1005+
string create_offer(CreateOfferRequest req);
1006+
1007+
[Throws=SendPaymentError]
1008+
SendPaymentResponse pay_offer(PayOfferRequest req);
9701009
};
9711010

9721011
namespace breez_sdk {

‎libs/sdk-bindings/src/uniffi_binding.rs

+31-23
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,31 @@ use breez_sdk_core::lnurl::pay::{LnUrlPayResult, LnUrlPaySuccessData};
55
use breez_sdk_core::{
66
error::*, mnemonic_to_seed as sdk_mnemonic_to_seed, parse as sdk_parse_input,
77
parse_invoice as sdk_parse_invoice, AesSuccessActionDataDecrypted, AesSuccessActionDataResult,
8-
BackupFailedData, BackupStatus, BitcoinAddressData, BreezEvent, BreezServices,
8+
Amount, BackupFailedData, BackupStatus, BitcoinAddressData, BreezEvent, BreezServices,
99
BuyBitcoinProvider, BuyBitcoinRequest, BuyBitcoinResponse, ChannelState, CheckMessageRequest,
1010
CheckMessageResponse, ClosedChannelPaymentDetails, Config, ConfigureNodeRequest,
11-
ConnectRequest, CurrencyInfo, EnvironmentType, EventListener, FeeratePreset, FiatCurrency,
12-
GreenlightCredentials, GreenlightDeviceCredentials, GreenlightNodeConfig, HealthCheckStatus,
13-
InputType, InvoicePaidDetails, LNInvoice, ListPaymentsRequest, LnPaymentDetails,
14-
LnUrlAuthError, LnUrlAuthRequestData, LnUrlCallbackStatus, LnUrlErrorData, LnUrlPayError,
15-
LnUrlPayErrorData, LnUrlPayRequest, LnUrlPayRequestData, LnUrlWithdrawError,
16-
LnUrlWithdrawRequest, LnUrlWithdrawRequestData, LnUrlWithdrawResult, LnUrlWithdrawSuccessData,
17-
LocaleOverrides, LocalizedName, LogEntry, LogStream, LspInformation,
18-
MaxReverseSwapAmountResponse, MessageSuccessActionData, MetadataFilter, MetadataItem, Network,
19-
NodeConfig, NodeCredentials, NodeState, OnchainPaymentLimitsResponse, OpenChannelFeeRequest,
20-
OpenChannelFeeResponse, OpeningFeeParams, OpeningFeeParamsMenu, PayOnchainRequest,
21-
PayOnchainResponse, Payment, PaymentDetails, PaymentFailedData, PaymentStatus, PaymentType,
22-
PaymentTypeFilter, PrepareOnchainPaymentRequest, PrepareOnchainPaymentResponse,
23-
PrepareRedeemOnchainFundsRequest, PrepareRedeemOnchainFundsResponse, PrepareRefundRequest,
24-
PrepareRefundResponse, Rate, ReceiveOnchainRequest, ReceivePaymentRequest,
25-
ReceivePaymentResponse, RecommendedFees, RedeemOnchainFundsRequest, RedeemOnchainFundsResponse,
26-
RefundRequest, RefundResponse, ReportIssueRequest, ReportPaymentFailureDetails,
27-
ReverseSwapFeesRequest, ReverseSwapInfo, ReverseSwapPairInfo, ReverseSwapStatus, RouteHint,
28-
RouteHintHop, SendOnchainRequest, SendOnchainResponse, SendPaymentRequest, SendPaymentResponse,
29-
SendSpontaneousPaymentRequest, ServiceHealthCheckResponse, SignMessageRequest,
30-
SignMessageResponse, StaticBackupRequest, StaticBackupResponse, SuccessActionProcessed,
31-
SwapAmountType, SwapInfo, SwapStatus, Symbol, TlvEntry, UnspentTransactionOutput,
32-
UrlSuccessActionData,
11+
ConnectRequest, CreateOfferRequest, CurrencyInfo, EnvironmentType, EventListener,
12+
FeeratePreset, FiatCurrency, GreenlightCredentials, GreenlightDeviceCredentials,
13+
GreenlightNodeConfig, HealthCheckStatus, InputType, InvoicePaidDetails, LNInvoice, LNOffer,
14+
ListPaymentsRequest, LnPaymentDetails, LnUrlAuthError, LnUrlAuthRequestData,
15+
LnUrlCallbackStatus, LnUrlErrorData, LnUrlPayError, LnUrlPayErrorData, LnUrlPayRequest,
16+
LnUrlPayRequestData, LnUrlWithdrawError, LnUrlWithdrawRequest, LnUrlWithdrawRequestData,
17+
LnUrlWithdrawResult, LnUrlWithdrawSuccessData, LocaleOverrides, LocalizedName, LogEntry,
18+
LogStream, LspInformation, MaxReverseSwapAmountResponse, MessageSuccessActionData,
19+
MetadataFilter, MetadataItem, Network, NodeConfig, NodeCredentials, NodeState,
20+
OnchainPaymentLimitsResponse, OpenChannelFeeRequest, OpenChannelFeeResponse, OpeningFeeParams,
21+
OpeningFeeParamsMenu, PayOfferRequest, PayOnchainRequest, PayOnchainResponse, Payment,
22+
PaymentDetails, PaymentFailedData, PaymentStatus, PaymentType, PaymentTypeFilter,
23+
PrepareOnchainPaymentRequest, PrepareOnchainPaymentResponse, PrepareRedeemOnchainFundsRequest,
24+
PrepareRedeemOnchainFundsResponse, PrepareRefundRequest, PrepareRefundResponse, Rate,
25+
ReceiveOnchainRequest, ReceivePaymentRequest, ReceivePaymentResponse, RecommendedFees,
26+
RedeemOnchainFundsRequest, RedeemOnchainFundsResponse, RefundRequest, RefundResponse,
27+
ReportIssueRequest, ReportPaymentFailureDetails, ReverseSwapFeesRequest, ReverseSwapInfo,
28+
ReverseSwapPairInfo, ReverseSwapStatus, RouteHint, RouteHintHop, SendOnchainRequest,
29+
SendOnchainResponse, SendPaymentRequest, SendPaymentResponse, SendSpontaneousPaymentRequest,
30+
ServiceHealthCheckResponse, SignMessageRequest, SignMessageResponse, StaticBackupRequest,
31+
StaticBackupResponse, SuccessActionProcessed, SwapAmountType, SwapInfo, SwapStatus, Symbol,
32+
TlvEntry, UnspentTransactionOutput, UrlSuccessActionData,
3333
};
3434
use log::{Level, LevelFilter, Metadata, Record};
3535
use once_cell::sync::{Lazy, OnceCell};
@@ -381,6 +381,14 @@ impl BlockingBreezServices {
381381
) -> SdkResult<PrepareRedeemOnchainFundsResponse> {
382382
rt().block_on(self.breez_services.prepare_redeem_onchain_funds(req))
383383
}
384+
385+
pub fn create_offer(&self, req: CreateOfferRequest) -> Result<String> {
386+
rt().block_on(self.breez_services.create_offer(req))
387+
}
388+
389+
pub fn pay_offer(&self, req: PayOfferRequest) -> Result<SendPaymentResponse, SendPaymentError> {
390+
rt().block_on(self.breez_services.pay_offer(req))
391+
}
384392
}
385393

386394
pub fn parse_invoice(invoice: String) -> SdkResult<LNInvoice> {

‎libs/sdk-common/src/input_parser.rs

+35
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ use anyhow::{anyhow, Result};
44
use bip21::Uri;
55
use bitcoin::bech32;
66
use bitcoin::bech32::FromBase32;
7+
use lightning::offers::offer::Offer;
78
use serde::{Deserialize, Serialize};
89
use LnUrlRequestData::*;
910

11+
use crate::prelude::InputType::Bolt12Offer;
1012
use crate::prelude::*;
1113

1214
/// Parses generic user input, typically pasted from clipboard or scanned from a QR.
@@ -149,6 +151,7 @@ use crate::prelude::*;
149151
/// }
150152
/// }
151153
/// ```
154+
152155
pub async fn parse(input: &str) -> Result<InputType> {
153156
let input = input.trim();
154157

@@ -179,6 +182,24 @@ pub async fn parse(input: &str) -> Result<InputType> {
179182
return Ok(InputType::Bolt11 { invoice });
180183
}
181184

185+
if let Ok(offer) = input.parse::<Offer>() {
186+
return Ok(Bolt12Offer {
187+
offer: LNOffer {
188+
bolt12: input.to_string(),
189+
chains: offer
190+
.chains()
191+
.iter()
192+
.map(|chain| chain.to_string())
193+
.collect(),
194+
amount: offer.amount().map(|amount| amount.clone().into()),
195+
description: offer.description().to_string(),
196+
absolute_expiry: offer.absolute_expiry().map(|expiry| expiry.as_secs()),
197+
issuer: offer.issuer().map(|s| s.to_string()),
198+
signing_pubkey: offer.signing_pubkey().to_string(),
199+
},
200+
});
201+
}
202+
182203
// Public key serialized in compressed form (66 hex chars)
183204
if let Ok(_node_id) = bitcoin::secp256k1::PublicKey::from_str(input) {
184205
return Ok(InputType::NodeId {
@@ -396,6 +417,9 @@ pub enum InputType {
396417
Bolt11 {
397418
invoice: LNInvoice,
398419
},
420+
Bolt12Offer {
421+
offer: LNOffer,
422+
},
399423
NodeId {
400424
node_id: String,
401425
},
@@ -786,6 +810,17 @@ pub(crate) mod tests {
786810
Ok(())
787811
}
788812

813+
#[tokio::test]
814+
async fn test_bolt12_offer() -> Result<()> {
815+
let offer = "lno1pqqnyzsmx5cx6umpwssx6atvw35j6ut4v9h8g6t50ysx7enxv4epyrmjw4ehgcm0wfczucm0d5hxzag5qqtzzq3lxgva5qlw9xsjmeqs0ek9cdj0vpec9ur972l7mywa66u3q7dlhs";
816+
817+
assert!(matches!(
818+
parse(offer).await?,
819+
InputType::Bolt12Offer { offer: _offer }
820+
));
821+
Ok(())
822+
}
823+
789824
#[tokio::test]
790825
async fn test_url() -> Result<()> {
791826
assert!(matches!(

‎libs/sdk-common/src/invoice.rs

+49-4
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ use std::time::{SystemTimeError, UNIX_EPOCH};
44
use bitcoin::secp256k1::{self, PublicKey};
55
use hex::ToHex;
66
use lightning::routing::gossip::RoutingFees;
7-
use lightning::routing::*;
8-
use lightning_invoice::*;
7+
use lightning::routing::router;
8+
use lightning_invoice::{
9+
Bolt11Invoice, Bolt11InvoiceDescription, Bolt11ParseError, Bolt11SemanticError, CreationError,
10+
InvoiceBuilder, RawBolt11Invoice, SignedRawBolt11Invoice,
11+
};
912
use regex::Regex;
1013
use serde::{Deserialize, Serialize};
1114

@@ -75,7 +78,49 @@ impl From<SystemTimeError> for InvoiceError {
7578
}
7679
}
7780

78-
/// Wrapper for a BOLT11 LN invoice
81+
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
82+
pub enum Amount {
83+
Bitcoin {
84+
amount_msat: u64,
85+
},
86+
Currency {
87+
// Reference to [FiatCurrency.id]
88+
iso4217_code: String,
89+
fractional_amount: u64,
90+
},
91+
}
92+
93+
impl From<lightning::offers::offer::Amount> for Amount {
94+
fn from(amount: lightning::offers::offer::Amount) -> Self {
95+
match amount {
96+
lightning::offers::offer::Amount::Bitcoin { amount_msats } => Amount::Bitcoin {
97+
amount_msat: amount_msats,
98+
},
99+
lightning::offers::offer::Amount::Currency {
100+
iso4217_code,
101+
amount,
102+
} => Amount::Currency {
103+
iso4217_code: String::from_utf8(iso4217_code.to_vec())
104+
.expect("Expecting a valid ISO 4217 character sequence"),
105+
fractional_amount: amount,
106+
},
107+
}
108+
}
109+
}
110+
111+
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
112+
pub struct LNOffer {
113+
pub bolt12: String,
114+
pub chains: Vec<String>,
115+
pub amount: Option<Amount>,
116+
pub description: String,
117+
pub absolute_expiry: Option<u64>,
118+
pub issuer: Option<String>,
119+
pub signing_pubkey: String,
120+
// pub paths: Vec<BlindedPath>,
121+
}
122+
123+
/// Wrapper for a BOLT11 invoice
79124
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
80125
pub struct LNInvoice {
81126
pub bolt11: String,
@@ -271,7 +316,7 @@ pub fn parse_invoice(bolt11: &str) -> InvoiceResult<LNInvoice> {
271316
let converted_hints = invoice_hints.iter().map(RouteHint::from_ldk_hint).collect();
272317
// return the parsed invoice
273318
let ln_invoice = LNInvoice {
274-
bolt11: bolt11.to_string(),
319+
bolt11: invoice.to_string(),
275320
network: invoice.network().into(),
276321
payee_pubkey,
277322
expiry: invoice.expiry_time().as_secs(),

0 commit comments

Comments
 (0)
Please sign in to comment.