Skip to content

Commit

Permalink
Squash and rebase Bolt12 implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
dangeross committed Jul 5, 2024
1 parent 8c27e8b commit ad384b1
Show file tree
Hide file tree
Showing 26 changed files with 2,881 additions and 212 deletions.
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

0 comments on commit ad384b1

Please sign in to comment.