Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding support for bolt12 offers #598

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions libs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions libs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ base64 = "0.13.0"
bitcoin = "=0.29.2" # Same version as used in gl-client
# Pin the reqwest dependency until macOS linker issue is fixed: https://github.com/seanmonstar/reqwest/issues/2006
hex = "0.4"
lightning = "=0.0.116" # Same version as used in gl-client
lightning-invoice = "=0.24.0" # Same version as used in gl-client
lightning = "=0.0.118" # Same version as used in gl-client
lightning-invoice = "=0.26.0" # Same version as used in gl-client
log = "0.4"
mockito = "1"
mockito = "1.4.0"
once_cell = "1"
prost = "^0.11"
regex = "1.8.1"
Expand Down
41 changes: 40 additions & 1 deletion libs/sdk-bindings/src/breez_sdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ enum SendPaymentError {
"InvalidInvoice",
"InvoiceExpired",
"InvalidNetwork",
"OfferChanged",
"PaymentFailed",
"PaymentTimeout",
"RouteNotFound",
Expand Down Expand Up @@ -481,6 +482,21 @@ dictionary StaticBackupResponse {
sequence<string>? backup;
};

dictionary CreateOfferRequest {
string description;
u64? amount_msat = null;
u64? absolute_expiry = null;
u64? quantity_max = null;
};

dictionary PayOfferRequest {
string offer;
u64? amount_msat = null;
f64? timeout = null;
string? payer_note = null;
string? label = null;
};

dictionary ReceiveOnchainRequest {
OpeningFeeParams? opening_fee_params = null;
};
Expand Down Expand Up @@ -716,10 +732,27 @@ dictionary RecommendedFees {
u64 minimum_fee;
};

[Enum]
interface Amount {
Bitcoin(u64 amount_msat);
Currency(string iso4217_code, u64 fractional_amount);
};

dictionary LNOffer {
string bolt12;
sequence<string> chains;
string description;
string signing_pubkey;
Amount? amount;
u64? absolute_expiry;
string? issuer;
};

[Enum]
interface InputType {
BitcoinAddress(BitcoinAddressData address);
Bolt11(LNInvoice invoice);
Bolt12Offer(LNOffer offer);
NodeId(string node_id);
Url(string url);
LnUrlPay(LnUrlPayRequestData data);
Expand Down Expand Up @@ -752,7 +785,7 @@ dictionary RedeemOnchainFundsResponse {
};

dictionary SendPaymentRequest {
string bolt11;
string invoice;
u64? amount_msat = null;
string? label = null;
};
Expand Down Expand Up @@ -967,6 +1000,12 @@ interface BlockingBreezServices {

[Throws=SdkError]
PrepareRedeemOnchainFundsResponse prepare_redeem_onchain_funds(PrepareRedeemOnchainFundsRequest req);

[Throws=SdkError]
string create_offer(CreateOfferRequest req);

[Throws=SendPaymentError]
SendPaymentResponse pay_offer(PayOfferRequest req);
};

namespace breez_sdk {
Expand Down
54 changes: 31 additions & 23 deletions libs/sdk-bindings/src/uniffi_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,31 @@ use breez_sdk_core::lnurl::pay::{LnUrlPayResult, LnUrlPaySuccessData};
use breez_sdk_core::{
error::*, mnemonic_to_seed as sdk_mnemonic_to_seed, parse as sdk_parse_input,
parse_invoice as sdk_parse_invoice, AesSuccessActionDataDecrypted, AesSuccessActionDataResult,
BackupFailedData, BackupStatus, BitcoinAddressData, BreezEvent, BreezServices,
Amount, BackupFailedData, BackupStatus, BitcoinAddressData, BreezEvent, BreezServices,
BuyBitcoinProvider, BuyBitcoinRequest, BuyBitcoinResponse, ChannelState, CheckMessageRequest,
CheckMessageResponse, ClosedChannelPaymentDetails, Config, ConfigureNodeRequest,
ConnectRequest, CurrencyInfo, EnvironmentType, EventListener, FeeratePreset, FiatCurrency,
GreenlightCredentials, GreenlightDeviceCredentials, GreenlightNodeConfig, HealthCheckStatus,
InputType, InvoicePaidDetails, LNInvoice, ListPaymentsRequest, LnPaymentDetails,
LnUrlAuthError, LnUrlAuthRequestData, LnUrlCallbackStatus, LnUrlErrorData, LnUrlPayError,
LnUrlPayErrorData, LnUrlPayRequest, LnUrlPayRequestData, LnUrlWithdrawError,
LnUrlWithdrawRequest, LnUrlWithdrawRequestData, LnUrlWithdrawResult, LnUrlWithdrawSuccessData,
LocaleOverrides, LocalizedName, LogEntry, LogStream, LspInformation,
MaxReverseSwapAmountResponse, MessageSuccessActionData, MetadataFilter, MetadataItem, Network,
NodeConfig, NodeCredentials, NodeState, OnchainPaymentLimitsResponse, OpenChannelFeeRequest,
OpenChannelFeeResponse, OpeningFeeParams, OpeningFeeParamsMenu, PayOnchainRequest,
PayOnchainResponse, Payment, PaymentDetails, PaymentFailedData, PaymentStatus, PaymentType,
PaymentTypeFilter, PrepareOnchainPaymentRequest, PrepareOnchainPaymentResponse,
PrepareRedeemOnchainFundsRequest, PrepareRedeemOnchainFundsResponse, PrepareRefundRequest,
PrepareRefundResponse, Rate, ReceiveOnchainRequest, ReceivePaymentRequest,
ReceivePaymentResponse, RecommendedFees, RedeemOnchainFundsRequest, RedeemOnchainFundsResponse,
RefundRequest, RefundResponse, ReportIssueRequest, ReportPaymentFailureDetails,
ReverseSwapFeesRequest, ReverseSwapInfo, ReverseSwapPairInfo, ReverseSwapStatus, RouteHint,
RouteHintHop, SendOnchainRequest, SendOnchainResponse, SendPaymentRequest, SendPaymentResponse,
SendSpontaneousPaymentRequest, ServiceHealthCheckResponse, SignMessageRequest,
SignMessageResponse, StaticBackupRequest, StaticBackupResponse, SuccessActionProcessed,
SwapAmountType, SwapInfo, SwapStatus, Symbol, TlvEntry, UnspentTransactionOutput,
UrlSuccessActionData,
ConnectRequest, CreateOfferRequest, CurrencyInfo, EnvironmentType, EventListener,
FeeratePreset, FiatCurrency, GreenlightCredentials, GreenlightDeviceCredentials,
GreenlightNodeConfig, HealthCheckStatus, InputType, InvoicePaidDetails, LNInvoice, LNOffer,
ListPaymentsRequest, LnPaymentDetails, LnUrlAuthError, LnUrlAuthRequestData,
LnUrlCallbackStatus, LnUrlErrorData, LnUrlPayError, LnUrlPayErrorData, LnUrlPayRequest,
LnUrlPayRequestData, LnUrlWithdrawError, LnUrlWithdrawRequest, LnUrlWithdrawRequestData,
LnUrlWithdrawResult, LnUrlWithdrawSuccessData, LocaleOverrides, LocalizedName, LogEntry,
LogStream, LspInformation, MaxReverseSwapAmountResponse, MessageSuccessActionData,
MetadataFilter, MetadataItem, Network, NodeConfig, NodeCredentials, NodeState,
OnchainPaymentLimitsResponse, OpenChannelFeeRequest, OpenChannelFeeResponse, OpeningFeeParams,
OpeningFeeParamsMenu, PayOfferRequest, PayOnchainRequest, PayOnchainResponse, Payment,
PaymentDetails, PaymentFailedData, PaymentStatus, PaymentType, PaymentTypeFilter,
PrepareOnchainPaymentRequest, PrepareOnchainPaymentResponse, PrepareRedeemOnchainFundsRequest,
PrepareRedeemOnchainFundsResponse, PrepareRefundRequest, PrepareRefundResponse, Rate,
ReceiveOnchainRequest, ReceivePaymentRequest, ReceivePaymentResponse, RecommendedFees,
RedeemOnchainFundsRequest, RedeemOnchainFundsResponse, RefundRequest, RefundResponse,
ReportIssueRequest, ReportPaymentFailureDetails, ReverseSwapFeesRequest, ReverseSwapInfo,
ReverseSwapPairInfo, ReverseSwapStatus, RouteHint, RouteHintHop, SendOnchainRequest,
SendOnchainResponse, SendPaymentRequest, SendPaymentResponse, SendSpontaneousPaymentRequest,
ServiceHealthCheckResponse, SignMessageRequest, SignMessageResponse, StaticBackupRequest,
StaticBackupResponse, SuccessActionProcessed, SwapAmountType, SwapInfo, SwapStatus, Symbol,
TlvEntry, UnspentTransactionOutput, UrlSuccessActionData,
};
use log::{Level, LevelFilter, Metadata, Record};
use once_cell::sync::{Lazy, OnceCell};
Expand Down Expand Up @@ -381,6 +381,14 @@ impl BlockingBreezServices {
) -> SdkResult<PrepareRedeemOnchainFundsResponse> {
rt().block_on(self.breez_services.prepare_redeem_onchain_funds(req))
}

pub fn create_offer(&self, req: CreateOfferRequest) -> Result<String> {
rt().block_on(self.breez_services.create_offer(req))
}

pub fn pay_offer(&self, req: PayOfferRequest) -> Result<SendPaymentResponse, SendPaymentError> {
rt().block_on(self.breez_services.pay_offer(req))
}
}

pub fn parse_invoice(invoice: String) -> SdkResult<LNInvoice> {
Expand Down
35 changes: 35 additions & 0 deletions libs/sdk-common/src/input_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ use anyhow::{anyhow, Result};
use bip21::Uri;
use bitcoin::bech32;
use bitcoin::bech32::FromBase32;
use lightning::offers::offer::Offer;
use serde::{Deserialize, Serialize};
use LnUrlRequestData::*;

use crate::prelude::InputType::Bolt12Offer;
use crate::prelude::*;

/// Parses generic user input, typically pasted from clipboard or scanned from a QR.
Expand Down Expand Up @@ -149,6 +151,7 @@ use crate::prelude::*;
/// }
/// }
/// ```

pub async fn parse(input: &str) -> Result<InputType> {
let input = input.trim();

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

if let Ok(offer) = input.parse::<Offer>() {
return Ok(Bolt12Offer {
offer: LNOffer {
bolt12: input.to_string(),
chains: offer
.chains()
.iter()
.map(|chain| chain.to_string())
.collect(),
amount: offer.amount().map(|amount| amount.clone().into()),
description: offer.description().to_string(),
absolute_expiry: offer.absolute_expiry().map(|expiry| expiry.as_secs()),
issuer: offer.issuer().map(|s| s.to_string()),
signing_pubkey: offer.signing_pubkey().to_string(),
},
});
}

// Public key serialized in compressed form (66 hex chars)
if let Ok(_node_id) = bitcoin::secp256k1::PublicKey::from_str(input) {
return Ok(InputType::NodeId {
Expand Down Expand Up @@ -396,6 +417,9 @@ pub enum InputType {
Bolt11 {
invoice: LNInvoice,
},
Bolt12Offer {
offer: LNOffer,
},
NodeId {
node_id: String,
},
Expand Down Expand Up @@ -786,6 +810,17 @@ pub(crate) mod tests {
Ok(())
}

#[tokio::test]
async fn test_bolt12_offer() -> Result<()> {
let offer = "lno1pqqnyzsmx5cx6umpwssx6atvw35j6ut4v9h8g6t50ysx7enxv4epyrmjw4ehgcm0wfczucm0d5hxzag5qqtzzq3lxgva5qlw9xsjmeqs0ek9cdj0vpec9ur972l7mywa66u3q7dlhs";

assert!(matches!(
parse(offer).await?,
InputType::Bolt12Offer { offer: _offer }
));
Ok(())
}

#[tokio::test]
async fn test_url() -> Result<()> {
assert!(matches!(
Expand Down
53 changes: 49 additions & 4 deletions libs/sdk-common/src/invoice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ use std::time::{SystemTimeError, UNIX_EPOCH};
use bitcoin::secp256k1::{self, PublicKey};
use hex::ToHex;
use lightning::routing::gossip::RoutingFees;
use lightning::routing::*;
use lightning_invoice::*;
use lightning::routing::router;
use lightning_invoice::{
Bolt11Invoice, Bolt11InvoiceDescription, Bolt11ParseError, Bolt11SemanticError, CreationError,
InvoiceBuilder, RawBolt11Invoice, SignedRawBolt11Invoice,
};
use regex::Regex;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -75,7 +78,49 @@ impl From<SystemTimeError> for InvoiceError {
}
}

/// Wrapper for a BOLT11 LN invoice
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum Amount {
Bitcoin {
amount_msat: u64,
},
Currency {
// Reference to [FiatCurrency.id]
iso4217_code: String,
fractional_amount: u64,
},
}

impl From<lightning::offers::offer::Amount> for Amount {
fn from(amount: lightning::offers::offer::Amount) -> Self {
match amount {
lightning::offers::offer::Amount::Bitcoin { amount_msats } => Amount::Bitcoin {
amount_msat: amount_msats,
},
lightning::offers::offer::Amount::Currency {
iso4217_code,
amount,
} => Amount::Currency {
iso4217_code: String::from_utf8(iso4217_code.to_vec())
.expect("Expecting a valid ISO 4217 character sequence"),
fractional_amount: amount,
},
}
}
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct LNOffer {
pub bolt12: String,
pub chains: Vec<String>,
pub amount: Option<Amount>,
pub description: String,
pub absolute_expiry: Option<u64>,
pub issuer: Option<String>,
pub signing_pubkey: String,
// pub paths: Vec<BlindedPath>,
}

/// Wrapper for a BOLT11 invoice
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct LNInvoice {
pub bolt11: String,
Expand Down Expand Up @@ -271,7 +316,7 @@ pub fn parse_invoice(bolt11: &str) -> InvoiceResult<LNInvoice> {
let converted_hints = invoice_hints.iter().map(RouteHint::from_ldk_hint).collect();
// return the parsed invoice
let ln_invoice = LNInvoice {
bolt11: bolt11.to_string(),
bolt11: invoice.to_string(),
network: invoice.network().into(),
payee_pubkey,
expiry: invoice.expiry_time().as_secs(),
Expand Down
Loading
Loading